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.
This commit is contained in:
@@ -39,6 +39,10 @@ import UserRepo from "../repositories/user.repository.js";
|
|||||||
|
|
||||||
export interface SyncedBookTools {
|
export interface SyncedBookTools {
|
||||||
lastUpdate: number;
|
lastUpdate: number;
|
||||||
|
charactersEnabled: boolean;
|
||||||
|
worldsEnabled: boolean;
|
||||||
|
locationsEnabled: boolean;
|
||||||
|
spellsEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BookToolsSettings {
|
export interface BookToolsSettings {
|
||||||
@@ -55,11 +59,12 @@ export interface BookProps {
|
|||||||
title: string;
|
title: string;
|
||||||
subTitle?: string;
|
subTitle?: string;
|
||||||
summary?: string;
|
summary?: string;
|
||||||
serieId?: number;
|
serieId?: number | null;
|
||||||
desiredReleaseDate?: string;
|
seriesId?: string | null;
|
||||||
desiredWordCount?: number;
|
desiredReleaseDate?: string | null;
|
||||||
|
desiredWordCount?: number | null;
|
||||||
wordCount?: number;
|
wordCount?: number;
|
||||||
coverImage?: string;
|
coverImage?: string | null;
|
||||||
bookMeta?: string;
|
bookMeta?: string;
|
||||||
tools?: BookToolsSettings;
|
tools?: BookToolsSettings;
|
||||||
}
|
}
|
||||||
@@ -145,6 +150,234 @@ export interface CompleteBookData {
|
|||||||
chapters: CompleteChapterContent[];
|
chapters: CompleteChapterContent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== SERIES TABLE INTERFACES (for sync) =====
|
||||||
|
|
||||||
|
export interface SeriesTable {
|
||||||
|
series_id: string;
|
||||||
|
user_id: string;
|
||||||
|
name: string;
|
||||||
|
hashed_name: string;
|
||||||
|
description: string | null;
|
||||||
|
cover_image: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesBooksTable {
|
||||||
|
series_id: string;
|
||||||
|
book_id: string;
|
||||||
|
book_order: number;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesCharactersTable {
|
||||||
|
character_id: string;
|
||||||
|
series_id: string;
|
||||||
|
user_id: string;
|
||||||
|
first_name: string;
|
||||||
|
last_name: string | null;
|
||||||
|
nickname: string | null;
|
||||||
|
age: number | 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 SeriesCharacterAttributesTable {
|
||||||
|
attr_id: string;
|
||||||
|
character_id: string;
|
||||||
|
user_id: string;
|
||||||
|
attribute_name: string;
|
||||||
|
attribute_value: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesWorldsTable {
|
||||||
|
world_id: string;
|
||||||
|
series_id: string;
|
||||||
|
user_id: string;
|
||||||
|
name: string;
|
||||||
|
hashed_name: string;
|
||||||
|
history: string | null;
|
||||||
|
politics: string | null;
|
||||||
|
economy: string | null;
|
||||||
|
religion: string | null;
|
||||||
|
languages: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesWorldElementsTable {
|
||||||
|
element_id: string;
|
||||||
|
world_id: string;
|
||||||
|
user_id: string;
|
||||||
|
element_type: number;
|
||||||
|
name: string;
|
||||||
|
original_name: string;
|
||||||
|
description: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesLocationsTable {
|
||||||
|
loc_id: string;
|
||||||
|
series_id: string;
|
||||||
|
user_id: string;
|
||||||
|
loc_name: string;
|
||||||
|
loc_original_name: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesLocationElementsTable {
|
||||||
|
element_id: string;
|
||||||
|
location_id: string;
|
||||||
|
user_id: string;
|
||||||
|
element_name: string;
|
||||||
|
original_name: string;
|
||||||
|
element_description: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesLocationSubElementsTable {
|
||||||
|
sub_element_id: string;
|
||||||
|
element_id: string;
|
||||||
|
user_id: string;
|
||||||
|
sub_elem_name: string;
|
||||||
|
original_name: string;
|
||||||
|
sub_elem_description: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesSpellsTable {
|
||||||
|
spell_id: string;
|
||||||
|
series_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 SeriesSpellTagsTable {
|
||||||
|
tag_id: string;
|
||||||
|
series_id: string;
|
||||||
|
user_id: string;
|
||||||
|
name: string;
|
||||||
|
hashed_name: string;
|
||||||
|
color: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== COMPLETE SERIES INTERFACE (for full sync) =====
|
||||||
|
|
||||||
|
export interface CompleteSeries {
|
||||||
|
series: SeriesTable[];
|
||||||
|
seriesBooks: SeriesBooksTable[];
|
||||||
|
seriesCharacters: SeriesCharactersTable[];
|
||||||
|
seriesCharacterAttributes: SeriesCharacterAttributesTable[];
|
||||||
|
seriesWorlds: SeriesWorldsTable[];
|
||||||
|
seriesWorldElements: SeriesWorldElementsTable[];
|
||||||
|
seriesLocations: SeriesLocationsTable[];
|
||||||
|
seriesLocationElements: SeriesLocationElementsTable[];
|
||||||
|
seriesLocationSubElements: SeriesLocationSubElementsTable[];
|
||||||
|
seriesSpells: SeriesSpellsTable[];
|
||||||
|
seriesSpellTags: SeriesSpellTagsTable[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== SYNCED SERIES INTERFACES (lightweight, for comparison) =====
|
||||||
|
|
||||||
|
export interface SyncedSeriesBook {
|
||||||
|
bookId: string;
|
||||||
|
order: number;
|
||||||
|
lastUpdate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesCharacterAttribute {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastUpdate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesCharacter {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastUpdate: number;
|
||||||
|
attributes: SyncedSeriesCharacterAttribute[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesWorldElement {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastUpdate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesWorld {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastUpdate: number;
|
||||||
|
elements: SyncedSeriesWorldElement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesLocationSubElement {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastUpdate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesLocationElement {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastUpdate: number;
|
||||||
|
subElements: SyncedSeriesLocationSubElement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesLocation {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastUpdate: number;
|
||||||
|
elements: SyncedSeriesLocationElement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesSpell {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastUpdate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesSpellTag {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastUpdate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeries {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastUpdate: number;
|
||||||
|
books: SyncedSeriesBook[];
|
||||||
|
characters: SyncedSeriesCharacter[];
|
||||||
|
worlds: SyncedSeriesWorld[];
|
||||||
|
locations: SyncedSeriesLocation[];
|
||||||
|
spells: SyncedSeriesSpell[];
|
||||||
|
spellTags: SyncedSeriesSpellTag[];
|
||||||
|
}
|
||||||
|
|
||||||
export default class Book {
|
export default class Book {
|
||||||
private readonly id: string;
|
private readonly id: string;
|
||||||
private type: string;
|
private type: string;
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ export interface CharacterPropsPost {
|
|||||||
name: string;
|
name: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
age: string;
|
age: number | null;
|
||||||
gender: string;
|
gender: string;
|
||||||
species: string;
|
species: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
status: 'alive' | 'dead' | 'unknown';
|
status: string;
|
||||||
category: CharacterCategory;
|
category: CharacterCategory;
|
||||||
title: string;
|
title: string;
|
||||||
image: string;
|
image: string;
|
||||||
@@ -48,6 +48,7 @@ export interface CharacterPropsPost {
|
|||||||
residence?: string;
|
residence?: string;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
|
seriesCharacterId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ export interface CharacterProps {
|
|||||||
name: string;
|
name: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
age: string;
|
age: number | null;
|
||||||
gender: string;
|
gender: string;
|
||||||
species: string;
|
species: string;
|
||||||
nationality: string;
|
nationality: string;
|
||||||
@@ -72,6 +73,7 @@ export interface CharacterProps {
|
|||||||
residence: string;
|
residence: string;
|
||||||
notes: string;
|
notes: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
seriesCharacterId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CharacterListResponse {
|
export interface CharacterListResponse {
|
||||||
@@ -83,24 +85,24 @@ export interface CompleteCharacterProps {
|
|||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
nickname?: string;
|
nickname: string;
|
||||||
age?: string;
|
age: number | null;
|
||||||
gender?: string;
|
gender: string;
|
||||||
species?: string;
|
species: string;
|
||||||
nationality?: string;
|
nationality: string;
|
||||||
status?: string;
|
status: string;
|
||||||
title: string;
|
title: string;
|
||||||
category: string;
|
category: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
role: string;
|
role: string;
|
||||||
biography: string;
|
biography: string;
|
||||||
history: string;
|
history: string;
|
||||||
speechPattern?: string;
|
speechPattern: string;
|
||||||
catchphrase?: string;
|
catchphrase: string;
|
||||||
residence?: string;
|
residence: string;
|
||||||
notes?: string;
|
notes: string;
|
||||||
color?: string;
|
color: string;
|
||||||
[key: string]: Attribute[] | string | undefined;
|
[key: string]: Attribute[] | string | number | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Attribute {
|
export interface Attribute {
|
||||||
@@ -152,7 +154,7 @@ export default class Character {
|
|||||||
name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '',
|
name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '',
|
||||||
lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_name, userEncryptionKey) : '',
|
lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_name, userEncryptionKey) : '',
|
||||||
nickname: encryptedCharacter.nickname ? System.decryptDataWithUserKey(encryptedCharacter.nickname, userEncryptionKey) : '',
|
nickname: encryptedCharacter.nickname ? System.decryptDataWithUserKey(encryptedCharacter.nickname, userEncryptionKey) : '',
|
||||||
age: encryptedCharacter.age ? System.decryptDataWithUserKey(encryptedCharacter.age, userEncryptionKey) : '',
|
age: encryptedCharacter.age ? parseInt(System.decryptDataWithUserKey(encryptedCharacter.age, userEncryptionKey), 10) : null,
|
||||||
gender: encryptedCharacter.gender ? System.decryptDataWithUserKey(encryptedCharacter.gender, userEncryptionKey) : '',
|
gender: encryptedCharacter.gender ? System.decryptDataWithUserKey(encryptedCharacter.gender, userEncryptionKey) : '',
|
||||||
species: encryptedCharacter.species ? System.decryptDataWithUserKey(encryptedCharacter.species, userEncryptionKey) : '',
|
species: encryptedCharacter.species ? System.decryptDataWithUserKey(encryptedCharacter.species, userEncryptionKey) : '',
|
||||||
nationality: encryptedCharacter.nationality ? System.decryptDataWithUserKey(encryptedCharacter.nationality, userEncryptionKey) : '',
|
nationality: encryptedCharacter.nationality ? System.decryptDataWithUserKey(encryptedCharacter.nationality, userEncryptionKey) : '',
|
||||||
@@ -168,6 +170,7 @@ export default class Character {
|
|||||||
residence: encryptedCharacter.residence ? System.decryptDataWithUserKey(encryptedCharacter.residence, userEncryptionKey) : '',
|
residence: encryptedCharacter.residence ? System.decryptDataWithUserKey(encryptedCharacter.residence, userEncryptionKey) : '',
|
||||||
notes: encryptedCharacter.notes ? System.decryptDataWithUserKey(encryptedCharacter.notes, userEncryptionKey) : '',
|
notes: encryptedCharacter.notes ? System.decryptDataWithUserKey(encryptedCharacter.notes, userEncryptionKey) : '',
|
||||||
color: encryptedCharacter.color ? System.decryptDataWithUserKey(encryptedCharacter.color, userEncryptionKey) : '',
|
color: encryptedCharacter.color ? System.decryptDataWithUserKey(encryptedCharacter.color, userEncryptionKey) : '',
|
||||||
|
seriesCharacterId: encryptedCharacter.series_character_id || null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return { characters: decryptedCharacterList, enabled };
|
return { characters: decryptedCharacterList, enabled };
|
||||||
@@ -191,7 +194,7 @@ export default class Character {
|
|||||||
firstName: System.encryptDataWithUserKey(character.name, userEncryptionKey),
|
firstName: System.encryptDataWithUserKey(character.name, userEncryptionKey),
|
||||||
lastName: System.encryptDataWithUserKey(character.lastName, userEncryptionKey),
|
lastName: System.encryptDataWithUserKey(character.lastName, userEncryptionKey),
|
||||||
nickname: System.encryptDataWithUserKey(character.nickname || '', userEncryptionKey),
|
nickname: System.encryptDataWithUserKey(character.nickname || '', userEncryptionKey),
|
||||||
age: System.encryptDataWithUserKey(character.age || '', userEncryptionKey),
|
age: character.age !== null ? System.encryptDataWithUserKey(String(character.age), userEncryptionKey) : '',
|
||||||
gender: System.encryptDataWithUserKey(character.gender || '', userEncryptionKey),
|
gender: System.encryptDataWithUserKey(character.gender || '', userEncryptionKey),
|
||||||
species: System.encryptDataWithUserKey(character.species || '', userEncryptionKey),
|
species: System.encryptDataWithUserKey(character.species || '', userEncryptionKey),
|
||||||
nationality: System.encryptDataWithUserKey(character.nationality || '', userEncryptionKey),
|
nationality: System.encryptDataWithUserKey(character.nationality || '', userEncryptionKey),
|
||||||
@@ -209,7 +212,7 @@ export default class Character {
|
|||||||
color: System.encryptDataWithUserKey(character.color || '', userEncryptionKey),
|
color: System.encryptDataWithUserKey(character.color || '', userEncryptionKey),
|
||||||
};
|
};
|
||||||
|
|
||||||
CharacterRepo.addNewCharacter(userId, characterId, characterData, bookId, lang);
|
CharacterRepo.addNewCharacter(userId, characterId, characterData, bookId, lang, character.seriesCharacterId || null);
|
||||||
const characterPropertyKeys: string[] = Object.keys(character);
|
const characterPropertyKeys: string[] = Object.keys(character);
|
||||||
for (const propertyKey of characterPropertyKeys) {
|
for (const propertyKey of characterPropertyKeys) {
|
||||||
if (Array.isArray(character[propertyKey as keyof CharacterPropsPost])) {
|
if (Array.isArray(character[propertyKey as keyof CharacterPropsPost])) {
|
||||||
@@ -244,7 +247,7 @@ export default class Character {
|
|||||||
firstName: System.encryptDataWithUserKey(character.name, userEncryptionKey),
|
firstName: System.encryptDataWithUserKey(character.name, userEncryptionKey),
|
||||||
lastName: System.encryptDataWithUserKey(character.lastName, userEncryptionKey),
|
lastName: System.encryptDataWithUserKey(character.lastName, userEncryptionKey),
|
||||||
nickname: System.encryptDataWithUserKey(character.nickname || '', userEncryptionKey),
|
nickname: System.encryptDataWithUserKey(character.nickname || '', userEncryptionKey),
|
||||||
age: System.encryptDataWithUserKey(character.age || '', userEncryptionKey),
|
age: character.age !== null ? System.encryptDataWithUserKey(String(character.age), userEncryptionKey) : '',
|
||||||
gender: System.encryptDataWithUserKey(character.gender || '', userEncryptionKey),
|
gender: System.encryptDataWithUserKey(character.gender || '', userEncryptionKey),
|
||||||
species: System.encryptDataWithUserKey(character.species || '', userEncryptionKey),
|
species: System.encryptDataWithUserKey(character.species || '', userEncryptionKey),
|
||||||
nationality: System.encryptDataWithUserKey(character.nationality || '', userEncryptionKey),
|
nationality: System.encryptDataWithUserKey(character.nationality || '', userEncryptionKey),
|
||||||
@@ -262,7 +265,7 @@ export default class Character {
|
|||||||
color: System.encryptDataWithUserKey(character.color || '', userEncryptionKey),
|
color: System.encryptDataWithUserKey(character.color || '', userEncryptionKey),
|
||||||
};
|
};
|
||||||
|
|
||||||
return CharacterRepo.updateCharacter(userId, character.id, characterData, System.timeStampInSeconds(), lang);
|
return CharacterRepo.updateCharacter(userId, character.id, characterData, System.timeStampInSeconds(), lang, character.seriesCharacterId || null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -370,7 +373,7 @@ export default class Character {
|
|||||||
name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '',
|
name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '',
|
||||||
lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_name, userEncryptionKey) : '',
|
lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_name, userEncryptionKey) : '',
|
||||||
nickname: encryptedCharacter.nickname ? System.decryptDataWithUserKey(encryptedCharacter.nickname as string, userEncryptionKey) : '',
|
nickname: encryptedCharacter.nickname ? System.decryptDataWithUserKey(encryptedCharacter.nickname as string, userEncryptionKey) : '',
|
||||||
age: encryptedCharacter.age ? System.decryptDataWithUserKey(encryptedCharacter.age as string, userEncryptionKey) : '',
|
age: encryptedCharacter.age ? parseInt(System.decryptDataWithUserKey(encryptedCharacter.age as string, userEncryptionKey), 10) : null,
|
||||||
gender: encryptedCharacter.gender ? System.decryptDataWithUserKey(encryptedCharacter.gender as string, userEncryptionKey) : '',
|
gender: encryptedCharacter.gender ? System.decryptDataWithUserKey(encryptedCharacter.gender as string, userEncryptionKey) : '',
|
||||||
species: encryptedCharacter.species ? System.decryptDataWithUserKey(encryptedCharacter.species as string, userEncryptionKey) : '',
|
species: encryptedCharacter.species ? System.decryptDataWithUserKey(encryptedCharacter.species as string, userEncryptionKey) : '',
|
||||||
nationality: encryptedCharacter.nationality ? System.decryptDataWithUserKey(encryptedCharacter.nationality as string, userEncryptionKey) : '',
|
nationality: encryptedCharacter.nationality ? System.decryptDataWithUserKey(encryptedCharacter.nationality as string, userEncryptionKey) : '',
|
||||||
@@ -442,11 +445,22 @@ export default class Character {
|
|||||||
uniqueCharactersMap.set(characterIdentifier, {
|
uniqueCharactersMap.set(characterIdentifier, {
|
||||||
name: character.name,
|
name: character.name,
|
||||||
lastName: character.lastName,
|
lastName: character.lastName,
|
||||||
category: character.category,
|
nickname: character.nickname,
|
||||||
|
age: character.age,
|
||||||
|
gender: character.gender,
|
||||||
|
species: character.species,
|
||||||
|
nationality: character.nationality,
|
||||||
|
status: character.status,
|
||||||
title: character.title,
|
title: character.title,
|
||||||
|
category: character.category,
|
||||||
role: character.role,
|
role: character.role,
|
||||||
biography: character.biography,
|
biography: character.biography,
|
||||||
history: character.history
|
history: character.history,
|
||||||
|
speechPattern: character.speechPattern,
|
||||||
|
catchphrase: character.catchphrase,
|
||||||
|
residence: character.residence,
|
||||||
|
notes: character.notes,
|
||||||
|
color: character.color
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,7 +486,7 @@ export default class Character {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(character).forEach((propertyKey: string): void => {
|
Object.keys(character).forEach((propertyKey: string): void => {
|
||||||
const propertyValue: string | Attribute[] | undefined = character[propertyKey];
|
const propertyValue = character[propertyKey];
|
||||||
if (Array.isArray(propertyValue) && propertyValue.length > 0) {
|
if (Array.isArray(propertyValue) && propertyValue.length > 0) {
|
||||||
const capitalizedPropertyKey: string = propertyKey.charAt(0).toUpperCase() + propertyKey.slice(1);
|
const capitalizedPropertyKey: string = propertyKey.charAt(0).toUpperCase() + propertyKey.slice(1);
|
||||||
const formattedAttributeValues: string = propertyValue.map((attributeItem: Attribute) => attributeItem.name).join(', ');
|
const formattedAttributeValues: string = propertyValue.map((attributeItem: Attribute) => attributeItem.name).join(', ');
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface LocationProps {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
elements: Element[];
|
elements: Element[];
|
||||||
|
seriesLocationId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LocationListResponse {
|
export interface LocationListResponse {
|
||||||
@@ -79,7 +80,8 @@ export default class Location {
|
|||||||
location = {
|
location = {
|
||||||
id: record.loc_id,
|
id: record.loc_id,
|
||||||
name: decryptedName,
|
name: decryptedName,
|
||||||
elements: []
|
elements: [],
|
||||||
|
seriesLocationId: record.series_location_id || null,
|
||||||
};
|
};
|
||||||
locationArray.push(location);
|
locationArray.push(location);
|
||||||
}
|
}
|
||||||
@@ -127,12 +129,12 @@ export default class Location {
|
|||||||
* @param existingLocationId - Optional existing location ID to use instead of generating a new one.
|
* @param existingLocationId - Optional existing location ID to use instead of generating a new one.
|
||||||
* @returns The ID of the created location.
|
* @returns The ID of the created location.
|
||||||
*/
|
*/
|
||||||
static addLocationSection(userId: string, locationName: string, bookId: string, lang: 'fr' | 'en' = 'fr', existingLocationId?: string): string {
|
static addLocationSection(userId: string, locationName: string, bookId: string, lang: 'fr' | 'en' = 'fr', existingLocationId?: string, seriesLocationId: string | null = null): string {
|
||||||
const userKey: string = getUserEncryptionKey(userId);
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
const hashedName: string = System.hashElement(locationName);
|
const hashedName: string = System.hashElement(locationName);
|
||||||
const encryptedName: string = System.encryptDataWithUserKey(locationName, userKey);
|
const encryptedName: string = System.encryptDataWithUserKey(locationName, userKey);
|
||||||
const locationId: string = existingLocationId || System.createUniqueId();
|
const locationId: string = existingLocationId || System.createUniqueId();
|
||||||
return LocationRepo.insertLocation(userId, locationId, bookId, encryptedName, hashedName, lang);
|
return LocationRepo.insertLocation(userId, locationId, bookId, encryptedName, hashedName, lang, seriesLocationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -202,6 +204,28 @@ export default class Location {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a location section with optional name change and series link.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param sectionId - The unique identifier of the section
|
||||||
|
* @param sectionName - The new name (optional)
|
||||||
|
* @param seriesLocationId - The series location ID to link (optional, null to unlink)
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||||
|
* @returns True if the update was successful
|
||||||
|
*/
|
||||||
|
static updateSectionWithSeriesLink(userId: string, sectionId: string, sectionName?: string, seriesLocationId?: string | null, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
let encryptedName: string | null = null;
|
||||||
|
let originalNameHash: string | null = null;
|
||||||
|
|
||||||
|
if (sectionName) {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
encryptedName = System.encryptDataWithUserKey(sectionName, userKey);
|
||||||
|
originalNameHash = System.hashElement(sectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocationRepo.updateSectionWithSeriesLink(userId, sectionId, encryptedName, originalNameHash, seriesLocationId ?? null, lang);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a location section and all its associated elements and sub-elements.
|
* Deletes a location section and all its associated elements and sub-elements.
|
||||||
* @param userId - The user's unique identifier.
|
* @param userId - The user's unique identifier.
|
||||||
|
|||||||
248
electron/database/models/Series.ts
Normal file
248
electron/database/models/Series.ts
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
import { getUserEncryptionKey } from "../keyManager.js";
|
||||||
|
import System from "../System.js";
|
||||||
|
import SeriesRepo, { SeriesBookResult, SeriesListItem, SeriesResult } from "../repositories/series.repo.js";
|
||||||
|
|
||||||
|
export interface SeriesProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
coverImage: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesDetailProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
coverImage: string | null;
|
||||||
|
books: SeriesBookProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesBookProps {
|
||||||
|
bookId: string;
|
||||||
|
title: string;
|
||||||
|
order: number;
|
||||||
|
coverImage: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesListItemProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
coverImage: string | null;
|
||||||
|
bookCount: number;
|
||||||
|
bookIds: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BooksOrderPost {
|
||||||
|
bookId: string;
|
||||||
|
order: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Series {
|
||||||
|
/**
|
||||||
|
* Gets the list of all series for a user.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The list of series with decrypted names and descriptions
|
||||||
|
*/
|
||||||
|
public static getSeriesList(userId: string, lang: 'fr' | 'en' = 'fr'): SeriesListItemProps[] {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const seriesResults: SeriesListItem[] = SeriesRepo.fetchUserSeries(userId, lang);
|
||||||
|
|
||||||
|
return seriesResults.map((seriesItem: SeriesListItem): SeriesListItemProps => ({
|
||||||
|
id: seriesItem.series_id,
|
||||||
|
name: System.decryptDataWithUserKey(seriesItem.name, userKey),
|
||||||
|
description: seriesItem.description ? System.decryptDataWithUserKey(seriesItem.description, userKey) : '',
|
||||||
|
coverImage: seriesItem.cover_image,
|
||||||
|
bookCount: seriesItem.book_count,
|
||||||
|
bookIds: seriesItem.book_ids ? seriesItem.book_ids.split(',') : []
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the detail of a series including its books.
|
||||||
|
* @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 The series detail with decrypted data
|
||||||
|
*/
|
||||||
|
public static getSeriesDetail(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesDetailProps {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
|
||||||
|
const seriesResult: SeriesResult | null = SeriesRepo.fetchSeriesById(userId, seriesId, lang);
|
||||||
|
if (!seriesResult) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const booksResult: SeriesBookResult[] = SeriesRepo.fetchSeriesBooks(userId, seriesId, lang);
|
||||||
|
|
||||||
|
const books: SeriesBookProps[] = booksResult.map((book: SeriesBookResult) => ({
|
||||||
|
bookId: book.book_id,
|
||||||
|
title: System.decryptDataWithUserKey(book.title, userKey),
|
||||||
|
order: book.book_order,
|
||||||
|
coverImage: book.cover_image
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: seriesResult.series_id,
|
||||||
|
name: System.decryptDataWithUserKey(seriesResult.name, userKey),
|
||||||
|
description: seriesResult.description ? System.decryptDataWithUserKey(seriesResult.description, userKey) : '',
|
||||||
|
coverImage: seriesResult.cover_image,
|
||||||
|
books
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param name - The name of the series
|
||||||
|
* @param description - The description of the series
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @param bookIds - Optional array of book IDs to add to the series
|
||||||
|
* @returns The created series ID
|
||||||
|
*/
|
||||||
|
public static createSeries(userId: string, name: string, description: string, lang: 'fr' | 'en' = 'fr', bookIds?: string[]): string {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const seriesId: string = System.createUniqueId();
|
||||||
|
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
const hashedName: string = System.hashElement(name);
|
||||||
|
const encryptedDescription: string | null = description ? System.encryptDataWithUserKey(description, userKey) : null;
|
||||||
|
|
||||||
|
SeriesRepo.insertSeries(seriesId, userId, encryptedName, hashedName, encryptedDescription, lang);
|
||||||
|
|
||||||
|
if (bookIds && bookIds.length > 0) {
|
||||||
|
for (let i: number = 0; i < bookIds.length; i++) {
|
||||||
|
SeriesRepo.addBookToSeries(seriesId, bookIds[i], i + 1, lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return seriesId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param name - The name of the series
|
||||||
|
* @param description - The description of the series
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the update was successful
|
||||||
|
*/
|
||||||
|
public static updateSeries(userId: string, seriesId: string, name: string, description: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
const hashedName: string = System.hashElement(name);
|
||||||
|
const encryptedDescription: string | null = description ? System.encryptDataWithUserKey(description, userKey) : null;
|
||||||
|
|
||||||
|
return SeriesRepo.updateSeries(userId, seriesId, encryptedName, hashedName, encryptedDescription, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a series.
|
||||||
|
* @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 True if the deletion was successful
|
||||||
|
*/
|
||||||
|
public static deleteSeries(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return SeriesRepo.deleteSeries(userId, seriesId, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a book to a series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param bookId - The unique identifier of the book
|
||||||
|
* @param order - The order of the book in the series
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the addition was successful
|
||||||
|
*/
|
||||||
|
public static addBookToSeries(userId: string, seriesId: string, bookId: string, order: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return SeriesRepo.addBookToSeries(seriesId, bookId, order, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a book from a series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param bookId - The unique identifier of the book
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the removal was successful
|
||||||
|
*/
|
||||||
|
public static removeBookFromSeries(userId: string, seriesId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return SeriesRepo.removeBookFromSeries(seriesId, bookId, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the order of books in a series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param booksOrder - An array of {bookId, order} objects
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the update was successful
|
||||||
|
*/
|
||||||
|
public static updateBooksOrder(userId: string, seriesId: string, booksOrder: BooksOrderPost[], lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return SeriesRepo.updateBooksOrder(seriesId, booksOrder, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the series ID for a book if it belongs to one.
|
||||||
|
* @param bookId - The unique identifier of the book
|
||||||
|
* @returns The series ID or null
|
||||||
|
*/
|
||||||
|
public static getSeriesIdForBook(bookId: string): string | null {
|
||||||
|
return SeriesRepo.getSeriesIdForBook(bookId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets only the books of a series (without series details).
|
||||||
|
* @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 The list of books in the series
|
||||||
|
*/
|
||||||
|
public static getSeriesBooks(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesBookProps[] {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
|
||||||
|
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const booksResult: SeriesBookResult[] = SeriesRepo.fetchSeriesBooks(userId, seriesId, lang);
|
||||||
|
|
||||||
|
return booksResult.map((book: SeriesBookResult): SeriesBookProps => ({
|
||||||
|
bookId: book.book_id,
|
||||||
|
title: System.decryptDataWithUserKey(book.title, userKey),
|
||||||
|
order: book.book_order,
|
||||||
|
coverImage: book.cover_image
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
276
electron/database/models/SeriesCharacter.ts
Normal file
276
electron/database/models/SeriesCharacter.ts
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
import { getUserEncryptionKey } from "../keyManager.js";
|
||||||
|
import System from "../System.js";
|
||||||
|
import SeriesCharacterRepo, { SeriesCharacterAttributeResult, SeriesCharacterResult } from "../repositories/series-character.repo.js";
|
||||||
|
|
||||||
|
export type CharacterCategory = 'Main' | 'Secondary' | 'Recurring';
|
||||||
|
|
||||||
|
export interface SeriesCharacterPropsPost {
|
||||||
|
id: string | null;
|
||||||
|
name: string;
|
||||||
|
lastName: string;
|
||||||
|
nickname: string;
|
||||||
|
age: number | null;
|
||||||
|
gender: string;
|
||||||
|
species: string;
|
||||||
|
nationality: string;
|
||||||
|
status: string;
|
||||||
|
category: CharacterCategory;
|
||||||
|
title: string;
|
||||||
|
image: string;
|
||||||
|
physical: { name: string }[];
|
||||||
|
psychological: { name: string }[];
|
||||||
|
relations: { name: string }[];
|
||||||
|
skills: { name: string }[];
|
||||||
|
weaknesses: { name: string }[];
|
||||||
|
strengths: { name: string }[];
|
||||||
|
goals: { name: string }[];
|
||||||
|
motivations: { name: string }[];
|
||||||
|
arc: { name: string }[];
|
||||||
|
secrets: { name: string }[];
|
||||||
|
fears: { name: string }[];
|
||||||
|
flaws: { name: string }[];
|
||||||
|
beliefs: { name: string }[];
|
||||||
|
conflicts: { name: string }[];
|
||||||
|
quotes: { name: string }[];
|
||||||
|
distinguishingMarks: { name: string }[];
|
||||||
|
items: { name: string }[];
|
||||||
|
affiliations: { name: string }[];
|
||||||
|
role: string;
|
||||||
|
biography?: string;
|
||||||
|
history?: string;
|
||||||
|
speechPattern?: string;
|
||||||
|
catchphrase?: string;
|
||||||
|
residence?: string;
|
||||||
|
notes?: string;
|
||||||
|
color?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesCharacterListProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
lastName: string;
|
||||||
|
nickname: string;
|
||||||
|
age: number | null;
|
||||||
|
gender: string;
|
||||||
|
species: string;
|
||||||
|
nationality: string;
|
||||||
|
status: string;
|
||||||
|
title: string;
|
||||||
|
category: string;
|
||||||
|
image: string;
|
||||||
|
role: string;
|
||||||
|
biography: string;
|
||||||
|
history: string;
|
||||||
|
speechPattern: string;
|
||||||
|
catchphrase: string;
|
||||||
|
residence: string;
|
||||||
|
notes: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesAttribute {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CharacterAttributesResponse {
|
||||||
|
attributes: SeriesAttribute[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SeriesCharacter {
|
||||||
|
/**
|
||||||
|
* Retrieves a list of characters for a specific series owned by a 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 Characters list
|
||||||
|
*/
|
||||||
|
public static getCharacterList(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharacterListProps[] {
|
||||||
|
const characters: SeriesCharacterResult[] = SeriesCharacterRepo.fetchCharacters(userId, seriesId, lang);
|
||||||
|
if (!characters || characters.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
|
||||||
|
return characters.map((character: SeriesCharacterResult): SeriesCharacterListProps => ({
|
||||||
|
id: character.character_id,
|
||||||
|
name: character.first_name ? System.decryptDataWithUserKey(character.first_name, userKey) : '',
|
||||||
|
lastName: character.last_name ? System.decryptDataWithUserKey(character.last_name, userKey) : '',
|
||||||
|
nickname: character.nickname ? System.decryptDataWithUserKey(character.nickname, userKey) : '',
|
||||||
|
age: character.age ? parseInt(System.decryptDataWithUserKey(character.age, userKey), 10) : null,
|
||||||
|
gender: character.gender ? System.decryptDataWithUserKey(character.gender, userKey) : '',
|
||||||
|
species: character.species ? System.decryptDataWithUserKey(character.species, userKey) : '',
|
||||||
|
nationality: character.nationality ? System.decryptDataWithUserKey(character.nationality, userKey) : '',
|
||||||
|
status: character.status ? System.decryptDataWithUserKey(character.status, userKey) : 'alive',
|
||||||
|
title: character.title ? System.decryptDataWithUserKey(character.title, userKey) : '',
|
||||||
|
category: character.category ? System.decryptDataWithUserKey(character.category, userKey) : '',
|
||||||
|
image: character.image ? System.decryptDataWithUserKey(character.image, userKey) : '',
|
||||||
|
role: character.role ? System.decryptDataWithUserKey(character.role, userKey) : '',
|
||||||
|
biography: character.biography ? System.decryptDataWithUserKey(character.biography, userKey) : '',
|
||||||
|
history: character.history ? System.decryptDataWithUserKey(character.history, userKey) : '',
|
||||||
|
speechPattern: character.speech_pattern ? System.decryptDataWithUserKey(character.speech_pattern, userKey) : '',
|
||||||
|
catchphrase: character.catchphrase ? System.decryptDataWithUserKey(character.catchphrase, userKey) : '',
|
||||||
|
residence: character.residence ? System.decryptDataWithUserKey(character.residence, userKey) : '',
|
||||||
|
notes: character.notes ? System.decryptDataWithUserKey(character.notes, userKey) : '',
|
||||||
|
color: character.color ? System.decryptDataWithUserKey(character.color, userKey) : '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new character to a series with all its attributes.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param character - The character data to create
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The newly created character's ID
|
||||||
|
*/
|
||||||
|
public static addNewCharacter(userId: string, character: SeriesCharacterPropsPost, seriesId: string, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const characterId: string = System.createUniqueId();
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(character.name, userKey);
|
||||||
|
const encryptedLastName: string | null = character.lastName ? System.encryptDataWithUserKey(character.lastName, userKey) : null;
|
||||||
|
const encryptedNickname: string | null = character.nickname ? System.encryptDataWithUserKey(character.nickname, userKey) : null;
|
||||||
|
const encryptedAge: string | null = character.age !== null ? System.encryptDataWithUserKey(String(character.age), userKey) : null;
|
||||||
|
const encryptedGender: string | null = character.gender ? System.encryptDataWithUserKey(character.gender, userKey) : null;
|
||||||
|
const encryptedSpecies: string | null = character.species ? System.encryptDataWithUserKey(character.species, userKey) : null;
|
||||||
|
const encryptedNationality: string | null = character.nationality ? System.encryptDataWithUserKey(character.nationality, userKey) : null;
|
||||||
|
const encryptedStatus: string | null = character.status ? System.encryptDataWithUserKey(character.status, userKey) : null;
|
||||||
|
const encryptedTitle: string | null = character.title ? System.encryptDataWithUserKey(character.title, userKey) : null;
|
||||||
|
const encryptedCategory: string | null = character.category ? System.encryptDataWithUserKey(character.category, userKey) : null;
|
||||||
|
const encryptedImage: string | null = character.image ? System.encryptDataWithUserKey(character.image, userKey) : null;
|
||||||
|
const encryptedRole: string | null = character.role ? System.encryptDataWithUserKey(character.role, userKey) : null;
|
||||||
|
const encryptedBiography: string | null = character.biography ? System.encryptDataWithUserKey(character.biography, userKey) : null;
|
||||||
|
const encryptedHistory: string | null = character.history ? System.encryptDataWithUserKey(character.history, userKey) : null;
|
||||||
|
const encryptedSpeechPattern: string | null = character.speechPattern ? System.encryptDataWithUserKey(character.speechPattern, userKey) : null;
|
||||||
|
const encryptedCatchphrase: string | null = character.catchphrase ? System.encryptDataWithUserKey(character.catchphrase, userKey) : null;
|
||||||
|
const encryptedResidence: string | null = character.residence ? System.encryptDataWithUserKey(character.residence, userKey) : null;
|
||||||
|
const encryptedNotes: string | null = character.notes ? System.encryptDataWithUserKey(character.notes, userKey) : null;
|
||||||
|
const encryptedColor: string | null = character.color ? System.encryptDataWithUserKey(character.color, userKey) : null;
|
||||||
|
|
||||||
|
SeriesCharacterRepo.addNewCharacter(userId, characterId, encryptedName, encryptedLastName, encryptedNickname, encryptedAge, encryptedGender, encryptedSpecies, encryptedNationality, encryptedStatus, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, encryptedSpeechPattern, encryptedCatchphrase, encryptedResidence, encryptedNotes, encryptedColor, seriesId, lang);
|
||||||
|
|
||||||
|
const attributeKeys: string[] = Object.keys(character);
|
||||||
|
for (const attributeKey of attributeKeys) {
|
||||||
|
const attributeValue = character[attributeKey as keyof SeriesCharacterPropsPost];
|
||||||
|
if (Array.isArray(attributeValue)) {
|
||||||
|
const attributeArray: { name: string }[] = attributeValue;
|
||||||
|
if (attributeArray.length > 0) {
|
||||||
|
for (const attributeItem of attributeArray) {
|
||||||
|
const attributeType: string = attributeKey;
|
||||||
|
const attributeName: string = attributeItem.name;
|
||||||
|
this.addNewAttribute(characterId, userId, attributeType, attributeName, lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return characterId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing character's information and attributes.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param character - The updated character data
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the update was successful
|
||||||
|
*/
|
||||||
|
public static updateCharacter(userId: string, character: SeriesCharacterPropsPost, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
if (!character.id) {
|
||||||
|
throw new Error(lang === 'fr' ? 'ID du personnage requis.' : 'Character ID required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const exists: boolean = SeriesCharacterRepo.isCharacterExist(userId, character.id, lang);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Personnage non trouvé.' : 'Character not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(character.name, userKey);
|
||||||
|
const encryptedLastName: string | null = character.lastName ? System.encryptDataWithUserKey(character.lastName, userKey) : null;
|
||||||
|
const encryptedNickname: string | null = character.nickname ? System.encryptDataWithUserKey(character.nickname, userKey) : null;
|
||||||
|
const encryptedAge: string | null = character.age !== null ? System.encryptDataWithUserKey(String(character.age), userKey) : null;
|
||||||
|
const encryptedGender: string | null = character.gender ? System.encryptDataWithUserKey(character.gender, userKey) : null;
|
||||||
|
const encryptedSpecies: string | null = character.species ? System.encryptDataWithUserKey(character.species, userKey) : null;
|
||||||
|
const encryptedNationality: string | null = character.nationality ? System.encryptDataWithUserKey(character.nationality, userKey) : null;
|
||||||
|
const encryptedStatus: string | null = character.status ? System.encryptDataWithUserKey(character.status, userKey) : null;
|
||||||
|
const encryptedTitle: string | null = character.title ? System.encryptDataWithUserKey(character.title, userKey) : null;
|
||||||
|
const encryptedCategory: string | null = character.category ? System.encryptDataWithUserKey(character.category, userKey) : null;
|
||||||
|
const encryptedImage: string | null = character.image ? System.encryptDataWithUserKey(character.image, userKey) : null;
|
||||||
|
const encryptedRole: string | null = character.role ? System.encryptDataWithUserKey(character.role, userKey) : null;
|
||||||
|
const encryptedBiography: string | null = character.biography ? System.encryptDataWithUserKey(character.biography, userKey) : null;
|
||||||
|
const encryptedHistory: string | null = character.history ? System.encryptDataWithUserKey(character.history, userKey) : null;
|
||||||
|
const encryptedSpeechPattern: string | null = character.speechPattern ? System.encryptDataWithUserKey(character.speechPattern, userKey) : null;
|
||||||
|
const encryptedCatchphrase: string | null = character.catchphrase ? System.encryptDataWithUserKey(character.catchphrase, userKey) : null;
|
||||||
|
const encryptedResidence: string | null = character.residence ? System.encryptDataWithUserKey(character.residence, userKey) : null;
|
||||||
|
const encryptedNotes: string | null = character.notes ? System.encryptDataWithUserKey(character.notes, userKey) : null;
|
||||||
|
const encryptedColor: string | null = character.color ? System.encryptDataWithUserKey(character.color, userKey) : null;
|
||||||
|
|
||||||
|
return SeriesCharacterRepo.updateCharacter(userId, character.id, encryptedName, encryptedLastName, encryptedNickname, encryptedAge, encryptedGender, encryptedSpecies, encryptedNationality, encryptedStatus, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, encryptedSpeechPattern, encryptedCatchphrase, encryptedResidence, encryptedNotes, encryptedColor, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a character from a series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param characterId - The unique identifier of the character
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the deletion was successful
|
||||||
|
*/
|
||||||
|
public static deleteCharacter(userId: string, characterId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const exists: boolean = SeriesCharacterRepo.isCharacterExist(userId, characterId, lang);
|
||||||
|
if (!exists) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Personnage non trouvé.' : 'Character not found.');
|
||||||
|
}
|
||||||
|
return SeriesCharacterRepo.deleteCharacter(userId, characterId, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new attribute to a character.
|
||||||
|
* @param characterId - The unique identifier of the character
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param type - The attribute type
|
||||||
|
* @param name - The attribute value
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The attribute ID
|
||||||
|
*/
|
||||||
|
public static addNewAttribute(characterId: string, userId: string, type: string, name: string, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const attributeId: string = System.createUniqueId();
|
||||||
|
const encryptedType: string = System.encryptDataWithUserKey(type, userKey);
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
|
||||||
|
SeriesCharacterRepo.insertAttribute(attributeId, characterId, userId, encryptedType, encryptedName, lang);
|
||||||
|
|
||||||
|
return attributeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an attribute from a character.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param attributeId - The unique identifier of the attribute
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the deletion was successful
|
||||||
|
*/
|
||||||
|
public static deleteAttribute(userId: string, attributeId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
return SeriesCharacterRepo.deleteAttribute(userId, attributeId, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all attributes for a character.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param characterId - The unique identifier of the character
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The character's attributes
|
||||||
|
*/
|
||||||
|
public static getCharacterAttributes(userId: string, characterId: string, lang: 'fr' | 'en' = 'fr'): CharacterAttributesResponse {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const attributesResult: SeriesCharacterAttributeResult[] = SeriesCharacterRepo.fetchAttributes(characterId, userId, lang);
|
||||||
|
|
||||||
|
const attributes: SeriesAttribute[] = attributesResult.map((attr) => ({
|
||||||
|
id: attr.attr_id,
|
||||||
|
name: System.decryptDataWithUserKey(attr.attribute_value, userKey)
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { attributes };
|
||||||
|
}
|
||||||
|
}
|
||||||
154
electron/database/models/SeriesLocation.ts
Normal file
154
electron/database/models/SeriesLocation.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import { getUserEncryptionKey } from "../keyManager.js";
|
||||||
|
import System from "../System.js";
|
||||||
|
import SeriesLocationRepo, { SeriesLocationResult, SeriesLocationElementResult, SeriesLocationSubElementResult } from "../repositories/series-location.repo.js";
|
||||||
|
|
||||||
|
export interface SeriesLocationSubElementProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesLocationElementProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
subElements: SeriesLocationSubElementProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesLocationListProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
elements: SeriesLocationElementProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SeriesLocation {
|
||||||
|
/**
|
||||||
|
* Retrieves all locations for a series with their elements and sub-elements.
|
||||||
|
* @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 The list of locations
|
||||||
|
*/
|
||||||
|
public static getLocationList(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationListProps[] {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const locationsResult: SeriesLocationResult[] = SeriesLocationRepo.fetchLocations(userId, seriesId, lang);
|
||||||
|
|
||||||
|
return locationsResult.map((loc): SeriesLocationListProps => {
|
||||||
|
const elementsResult: SeriesLocationElementResult[] = SeriesLocationRepo.fetchElements(userId, loc.loc_id, lang);
|
||||||
|
|
||||||
|
const elements: SeriesLocationElementProps[] = elementsResult.map((elem): SeriesLocationElementProps => {
|
||||||
|
const subElementsResult: SeriesLocationSubElementResult[] = SeriesLocationRepo.fetchSubElements(userId, elem.element_id, lang);
|
||||||
|
|
||||||
|
const subElements: SeriesLocationSubElementProps[] = subElementsResult.map((sub): SeriesLocationSubElementProps => ({
|
||||||
|
id: sub.sub_element_id,
|
||||||
|
name: sub.sub_elem_name ? System.decryptDataWithUserKey(sub.sub_elem_name, userKey) : '',
|
||||||
|
description: sub.sub_elem_description ? System.decryptDataWithUserKey(sub.sub_elem_description, userKey) : ''
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: elem.element_id,
|
||||||
|
name: elem.element_name ? System.decryptDataWithUserKey(elem.element_name, userKey) : '',
|
||||||
|
description: elem.element_description ? System.decryptDataWithUserKey(elem.element_description, userKey) : '',
|
||||||
|
subElements
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: loc.loc_id,
|
||||||
|
name: loc.loc_name ? System.decryptDataWithUserKey(loc.loc_name, userKey) : '',
|
||||||
|
elements
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new location section to a series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param name - The name of the location
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The new location ID
|
||||||
|
*/
|
||||||
|
public static addLocationSection(userId: string, seriesId: string, name: string, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const locationId: string = System.createUniqueId();
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
const originalName: string = System.hashElement(name);
|
||||||
|
|
||||||
|
SeriesLocationRepo.insertLocation(locationId, seriesId, userId, encryptedName, originalName, lang);
|
||||||
|
return locationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new element to a location.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param locationId - The unique identifier of the location
|
||||||
|
* @param name - The name of the element
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @param description - The description of the element (optional)
|
||||||
|
* @returns The new element ID
|
||||||
|
*/
|
||||||
|
public static addElement(userId: string, locationId: string, name: string, lang: 'fr' | 'en' = 'fr', description?: string): string {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const elementId: string = System.createUniqueId();
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
const originalName: string = System.hashElement(name);
|
||||||
|
const encryptedDescription: string | null = description ? System.encryptDataWithUserKey(description, userKey) : null;
|
||||||
|
|
||||||
|
SeriesLocationRepo.insertElement(elementId, locationId, userId, encryptedName, originalName, encryptedDescription, lang);
|
||||||
|
return elementId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new sub-element to an element.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param elementId - The unique identifier of the element
|
||||||
|
* @param name - The name of the sub-element
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @param description - The description of the sub-element (optional)
|
||||||
|
* @returns The new sub-element ID
|
||||||
|
*/
|
||||||
|
public static addSubElement(userId: string, elementId: string, name: string, lang: 'fr' | 'en' = 'fr', description?: string): string {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const subElementId: string = System.createUniqueId();
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
const originalName: string = System.hashElement(name);
|
||||||
|
const encryptedDescription: string | null = description ? System.encryptDataWithUserKey(description, userKey) : null;
|
||||||
|
|
||||||
|
SeriesLocationRepo.insertSubElement(subElementId, elementId, userId, encryptedName, originalName, encryptedDescription, lang);
|
||||||
|
return subElementId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a location section.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param locationId - The unique identifier of the location
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if successful
|
||||||
|
*/
|
||||||
|
public static deleteLocation(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
return SeriesLocationRepo.deleteLocation(userId, locationId, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an element.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param elementId - The unique identifier of the element
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if successful
|
||||||
|
*/
|
||||||
|
public static deleteElement(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
return SeriesLocationRepo.deleteElement(userId, elementId, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a sub-element.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param subElementId - The unique identifier of the sub-element
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if successful
|
||||||
|
*/
|
||||||
|
public static deleteSubElement(userId: string, subElementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
return SeriesLocationRepo.deleteSubElement(userId, subElementId, lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
211
electron/database/models/SeriesSpell.ts
Normal file
211
electron/database/models/SeriesSpell.ts
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import { getUserEncryptionKey } from "../keyManager.js";
|
||||||
|
import System from "../System.js";
|
||||||
|
import SeriesSpellRepo, { SeriesSpellResult, SeriesSpellTagResult } from "../repositories/series-spell.repo.js";
|
||||||
|
|
||||||
|
export interface SeriesSpellTagProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
color: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesSpellListProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesSpellListResponse {
|
||||||
|
spells: SeriesSpellListProps[];
|
||||||
|
tags: SeriesSpellTagProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesSpellDetailProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
appearance: string;
|
||||||
|
tags: string[];
|
||||||
|
powerLevel: string | null;
|
||||||
|
components: string | null;
|
||||||
|
limitations: string | null;
|
||||||
|
notes: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SeriesSpell {
|
||||||
|
/**
|
||||||
|
* Retrieves all spells and tags for a series.
|
||||||
|
* @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 The list of spells and tags
|
||||||
|
*/
|
||||||
|
public static getSpellList(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellListResponse {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const spellsResult: SeriesSpellResult[] = SeriesSpellRepo.fetchSpells(userId, seriesId, lang);
|
||||||
|
const tagsResult: SeriesSpellTagResult[] = SeriesSpellRepo.fetchTags(userId, seriesId, lang);
|
||||||
|
|
||||||
|
const spells: SeriesSpellListProps[] = spellsResult.map((spell): SeriesSpellListProps => ({
|
||||||
|
id: spell.spell_id,
|
||||||
|
name: spell.name ? System.decryptDataWithUserKey(spell.name, userKey) : '',
|
||||||
|
description: spell.description ? System.decryptDataWithUserKey(spell.description, userKey) : '',
|
||||||
|
tags: spell.tags ? JSON.parse(System.decryptDataWithUserKey(spell.tags, userKey)) : []
|
||||||
|
}));
|
||||||
|
|
||||||
|
const tags: SeriesSpellTagProps[] = tagsResult.map((tag): SeriesSpellTagProps => ({
|
||||||
|
id: tag.tag_id,
|
||||||
|
name: tag.name ? System.decryptDataWithUserKey(tag.name, userKey) : '',
|
||||||
|
color: tag.color
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { spells, tags };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the 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 details
|
||||||
|
*/
|
||||||
|
public static getSpellDetail(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellDetailProps {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const spell: SeriesSpellResult | null = SeriesSpellRepo.fetchSpellById(userId, spellId, lang);
|
||||||
|
|
||||||
|
if (!spell) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Sort non trouvé.' : 'Spell not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: spell.spell_id,
|
||||||
|
name: spell.name ? System.decryptDataWithUserKey(spell.name, userKey) : '',
|
||||||
|
description: spell.description ? System.decryptDataWithUserKey(spell.description, userKey) : '',
|
||||||
|
appearance: spell.appearance ? System.decryptDataWithUserKey(spell.appearance, userKey) : '',
|
||||||
|
tags: spell.tags ? JSON.parse(System.decryptDataWithUserKey(spell.tags, userKey)) : [],
|
||||||
|
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 series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param name - The spell name
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @param description - The spell description
|
||||||
|
* @param appearance - The spell appearance
|
||||||
|
* @param tags - The spell tags
|
||||||
|
* @param powerLevel - The spell power level
|
||||||
|
* @param components - The spell components
|
||||||
|
* @param limitations - The spell limitations
|
||||||
|
* @param notes - The spell notes
|
||||||
|
* @returns The new spell ID
|
||||||
|
*/
|
||||||
|
public static addSpell(userId: string, seriesId: string, name: string, lang: 'fr' | 'en' = 'fr', description?: string | null, appearance?: string | null, tags?: string[], powerLevel?: string | null, components?: string | null, limitations?: string | null, notes?: string | null): string {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const spellId: string = System.createUniqueId();
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
const nameHash: string = System.hashElement(name);
|
||||||
|
const encryptedDescription: string = description ? System.encryptDataWithUserKey(description, userKey) : '';
|
||||||
|
const encryptedAppearance: string = appearance ? 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;
|
||||||
|
|
||||||
|
SeriesSpellRepo.insertSpell(spellId, seriesId, userId, encryptedName, nameHash, encryptedDescription, encryptedAppearance, encryptedTags, encryptedPowerLevel, encryptedComponents, encryptedLimitations, encryptedNotes, lang);
|
||||||
|
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 spell name
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @param description - The spell description
|
||||||
|
* @param appearance - The spell appearance
|
||||||
|
* @param tags - The spell tags
|
||||||
|
* @param powerLevel - The spell power level
|
||||||
|
* @param components - The spell components
|
||||||
|
* @param limitations - The spell limitations
|
||||||
|
* @param notes - The spell notes
|
||||||
|
* @returns True if successful
|
||||||
|
*/
|
||||||
|
public static updateSpell(userId: string, spellId: string, name: string, lang: 'fr' | 'en' = 'fr', description?: string | null, appearance?: string | null, tags?: string[], powerLevel?: string | null, components?: string | null, limitations?: string | null, notes?: string | null): boolean {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
const nameHash: string = System.hashElement(name);
|
||||||
|
const encryptedDescription: string = description ? System.encryptDataWithUserKey(description, userKey) : '';
|
||||||
|
const encryptedAppearance: string = appearance ? 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 SeriesSpellRepo.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 successful
|
||||||
|
*/
|
||||||
|
public static deleteSpell(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
return SeriesSpellRepo.deleteSpell(userId, spellId, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new tag to a series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param name - The name of the tag
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @param color - The color of the tag (optional)
|
||||||
|
* @returns The new tag ID
|
||||||
|
*/
|
||||||
|
public static addTag(userId: string, seriesId: string, name: string, lang: 'fr' | 'en' = 'fr', color?: string | null): string {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const tagId: string = System.createUniqueId();
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
const hashedName: string = System.hashElement(name);
|
||||||
|
|
||||||
|
SeriesSpellRepo.insertTag(tagId, seriesId, userId, encryptedName, hashedName, color || null, lang);
|
||||||
|
return tagId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing 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 lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @param color - The new color of the tag (optional)
|
||||||
|
* @returns True if successful
|
||||||
|
*/
|
||||||
|
public static updateTag(userId: string, tagId: string, name: string, lang: 'fr' | 'en' = 'fr', color?: string | null): boolean {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
const hashedName: string = System.hashElement(name);
|
||||||
|
|
||||||
|
return SeriesSpellRepo.updateTag(userId, tagId, encryptedName, hashedName, color || null, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a tag.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param tagId - The unique identifier of the tag
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if successful
|
||||||
|
*/
|
||||||
|
public static deleteTag(userId: string, tagId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
return SeriesSpellRepo.deleteTag(userId, tagId, lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
199
electron/database/models/SeriesSync.ts
Normal file
199
electron/database/models/SeriesSync.ts
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import { getUserEncryptionKey } from "../keyManager.js";
|
||||||
|
import System from "../System.js";
|
||||||
|
import SeriesSyncRepo, { SyncElementType } from "../repositories/series-sync.repo.js";
|
||||||
|
|
||||||
|
export interface SeriesSyncUploadPayload {
|
||||||
|
type: SyncElementType;
|
||||||
|
bookElementId: string;
|
||||||
|
field: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesSyncResult {
|
||||||
|
success: boolean;
|
||||||
|
updatedCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SeriesSync {
|
||||||
|
/**
|
||||||
|
* Uploads a field value from a book element to its linked series element,
|
||||||
|
* and propagates the change to all other book elements linked to the same series element.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param payload - The upload payload containing type, bookElementId, field, and value
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The upload response
|
||||||
|
*/
|
||||||
|
public static uploadFieldToSeries(userId: string, payload: SeriesSyncUploadPayload, lang: 'fr' | 'en' = 'fr'): SeriesSyncResult {
|
||||||
|
const { type, bookElementId, field, value } = payload;
|
||||||
|
|
||||||
|
const seriesElementId: string | null = this.getSeriesLink(userId, type, bookElementId, lang);
|
||||||
|
if (!seriesElementId) {
|
||||||
|
throw new Error(lang === 'fr' ? `Cet élément n'est pas lié à une série.` : `This element is not linked to a series.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const encryptedValue: string = value ? System.encryptDataWithUserKey(value, userKey) : '';
|
||||||
|
|
||||||
|
const dbField: string = this.mapFieldToDbColumn(type, field);
|
||||||
|
|
||||||
|
this.updateSeriesElement(userId, type, seriesElementId, dbField, encryptedValue, lang);
|
||||||
|
const updatedCount: number = this.updateLinkedBookElements(userId, type, seriesElementId, dbField, encryptedValue, lang);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
updatedCount: updatedCount + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the series element ID linked to a book element.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param type - The type of element (character, world, location, spell)
|
||||||
|
* @param bookElementId - The unique identifier of the book element
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The series element ID or null if not linked
|
||||||
|
*/
|
||||||
|
private static getSeriesLink(userId: string, type: SyncElementType, bookElementId: string, lang: 'fr' | 'en'): string | null {
|
||||||
|
switch (type) {
|
||||||
|
case 'character':
|
||||||
|
return SeriesSyncRepo.getCharacterSeriesLink(userId, bookElementId, lang);
|
||||||
|
case 'world':
|
||||||
|
return SeriesSyncRepo.getWorldSeriesLink(userId, bookElementId, lang);
|
||||||
|
case 'location':
|
||||||
|
return SeriesSyncRepo.getLocationSeriesLink(userId, bookElementId, lang);
|
||||||
|
case 'spell':
|
||||||
|
return SeriesSyncRepo.getSpellSeriesLink(userId, bookElementId, lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps frontend field names to database column names.
|
||||||
|
* @param type - The type of element (character, world, location, spell)
|
||||||
|
* @param field - The frontend field name to map
|
||||||
|
* @returns The corresponding database column name
|
||||||
|
*/
|
||||||
|
private static mapFieldToDbColumn(type: SyncElementType, field: string): string {
|
||||||
|
const characterFieldMap: Record<string, string> = {
|
||||||
|
'name': 'first_name',
|
||||||
|
'firstName': 'first_name',
|
||||||
|
'lastName': 'last_name',
|
||||||
|
'nickname': 'nickname',
|
||||||
|
'age': 'age',
|
||||||
|
'gender': 'gender',
|
||||||
|
'species': 'species',
|
||||||
|
'nationality': 'nationality',
|
||||||
|
'status': 'status',
|
||||||
|
'title': 'title',
|
||||||
|
'category': 'category',
|
||||||
|
'role': 'role',
|
||||||
|
'biography': 'biography',
|
||||||
|
'history': 'history',
|
||||||
|
'speechPattern': 'speech_pattern',
|
||||||
|
'catchphrase': 'catchphrase',
|
||||||
|
'residence': 'residence',
|
||||||
|
'notes': 'notes',
|
||||||
|
'color': 'color'
|
||||||
|
};
|
||||||
|
|
||||||
|
const worldFieldMap: Record<string, string> = {
|
||||||
|
'name': 'name',
|
||||||
|
'history': 'history',
|
||||||
|
'politics': 'politics',
|
||||||
|
'economy': 'economy',
|
||||||
|
'religion': 'religion',
|
||||||
|
'languages': 'languages'
|
||||||
|
};
|
||||||
|
|
||||||
|
const locationFieldMap: Record<string, string> = {
|
||||||
|
'name': 'name',
|
||||||
|
'loc_name': 'loc_name'
|
||||||
|
};
|
||||||
|
|
||||||
|
const spellFieldMap: Record<string, string> = {
|
||||||
|
'name': 'name',
|
||||||
|
'description': 'description',
|
||||||
|
'type': 'type',
|
||||||
|
'level': 'level',
|
||||||
|
'range': 'range',
|
||||||
|
'duration': 'duration',
|
||||||
|
'cost': 'cost',
|
||||||
|
'effect': 'effect',
|
||||||
|
'components': 'components',
|
||||||
|
'notes': 'notes'
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'character':
|
||||||
|
return characterFieldMap[field] || field;
|
||||||
|
case 'world':
|
||||||
|
return worldFieldMap[field] || field;
|
||||||
|
case 'location':
|
||||||
|
return locationFieldMap[field] || field;
|
||||||
|
case 'spell':
|
||||||
|
return spellFieldMap[field] || field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the series element field.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param type - The type of element (character, world, location, spell)
|
||||||
|
* @param seriesElementId - The unique identifier of the series element
|
||||||
|
* @param field - The database column name to update
|
||||||
|
* @param encryptedValue - The encrypted value to set
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if updated successfully
|
||||||
|
*/
|
||||||
|
private static updateSeriesElement(userId: string, type: SyncElementType, seriesElementId: string, field: string, encryptedValue: string, lang: 'fr' | 'en'): boolean {
|
||||||
|
switch (type) {
|
||||||
|
case 'character':
|
||||||
|
return SeriesSyncRepo.updateSeriesCharacterField(userId, seriesElementId, field, encryptedValue, lang);
|
||||||
|
case 'world':
|
||||||
|
return SeriesSyncRepo.updateSeriesWorldField(userId, seriesElementId, field, encryptedValue, lang);
|
||||||
|
case 'location':
|
||||||
|
return SeriesSyncRepo.updateSeriesLocationField(userId, seriesElementId, field, encryptedValue, lang);
|
||||||
|
case 'spell':
|
||||||
|
return SeriesSyncRepo.updateSeriesSpellField(userId, seriesElementId, field, encryptedValue, lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates all book elements linked to the series element.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param type - The type of element (character, world, location, spell)
|
||||||
|
* @param seriesElementId - The unique identifier of the series element
|
||||||
|
* @param field - The database column name to update
|
||||||
|
* @param encryptedValue - The encrypted value to set
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The number of book elements updated
|
||||||
|
*/
|
||||||
|
private static updateLinkedBookElements(userId: string, type: SyncElementType, seriesElementId: string, field: string, encryptedValue: string, lang: 'fr' | 'en'): number {
|
||||||
|
const bookField: string = this.mapSeriesFieldToBookField(type, field);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'character':
|
||||||
|
return SeriesSyncRepo.updateLinkedBookCharactersField(userId, seriesElementId, bookField, encryptedValue, lang);
|
||||||
|
case 'world':
|
||||||
|
return SeriesSyncRepo.updateLinkedBookWorldsField(userId, seriesElementId, bookField, encryptedValue, lang);
|
||||||
|
case 'location':
|
||||||
|
return SeriesSyncRepo.updateLinkedBookLocationsField(userId, seriesElementId, bookField, encryptedValue, lang);
|
||||||
|
case 'spell':
|
||||||
|
return SeriesSyncRepo.updateLinkedBookSpellsField(userId, seriesElementId, bookField, encryptedValue, lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps series table field names to book table field names (if different).
|
||||||
|
* @param type - The type of element (character, world, location, spell)
|
||||||
|
* @param seriesField - The series table field name
|
||||||
|
* @returns The corresponding book table field name
|
||||||
|
*/
|
||||||
|
private static mapSeriesFieldToBookField(type: SyncElementType, seriesField: string): string {
|
||||||
|
if (type === 'location') {
|
||||||
|
if (seriesField === 'name') {
|
||||||
|
return 'loc_name';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return seriesField;
|
||||||
|
}
|
||||||
|
}
|
||||||
190
electron/database/models/SeriesWorld.ts
Normal file
190
electron/database/models/SeriesWorld.ts
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import { getUserEncryptionKey } from "../keyManager.js";
|
||||||
|
import System from "../System.js";
|
||||||
|
import SeriesWorldRepo, { SeriesWorldResult } from "../repositories/series-world.repo.js";
|
||||||
|
|
||||||
|
export interface SeriesWorldElementProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesWorldListProps {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
history: string;
|
||||||
|
politics: string;
|
||||||
|
economy: string;
|
||||||
|
religion: string;
|
||||||
|
languages: string;
|
||||||
|
laws: SeriesWorldElementProps[];
|
||||||
|
biomes: SeriesWorldElementProps[];
|
||||||
|
issues: SeriesWorldElementProps[];
|
||||||
|
customs: SeriesWorldElementProps[];
|
||||||
|
kingdoms: SeriesWorldElementProps[];
|
||||||
|
climate: SeriesWorldElementProps[];
|
||||||
|
resources: SeriesWorldElementProps[];
|
||||||
|
wildlife: SeriesWorldElementProps[];
|
||||||
|
arts: SeriesWorldElementProps[];
|
||||||
|
ethnicGroups: SeriesWorldElementProps[];
|
||||||
|
socialClasses: SeriesWorldElementProps[];
|
||||||
|
importantCharacters: SeriesWorldElementProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesWorldUpdateProps {
|
||||||
|
name: string;
|
||||||
|
history?: string;
|
||||||
|
politics?: string;
|
||||||
|
economy?: string;
|
||||||
|
religion?: string;
|
||||||
|
languages?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ELEMENT_TYPE_MAP: Record<number, keyof SeriesWorldListProps> = {
|
||||||
|
0: 'laws',
|
||||||
|
1: 'biomes',
|
||||||
|
2: 'issues',
|
||||||
|
3: 'customs',
|
||||||
|
4: 'kingdoms',
|
||||||
|
5: 'climate',
|
||||||
|
6: 'resources',
|
||||||
|
7: 'wildlife',
|
||||||
|
8: 'arts',
|
||||||
|
9: 'ethnicGroups',
|
||||||
|
10: 'socialClasses',
|
||||||
|
11: 'importantCharacters'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class SeriesWorld {
|
||||||
|
/**
|
||||||
|
* Retrieves all worlds and their elements for a series.
|
||||||
|
* @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 The list of worlds
|
||||||
|
*/
|
||||||
|
public static getWorldList(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldListProps[] {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const worldsResult: SeriesWorldResult[] = SeriesWorldRepo.fetchWorlds(userId, seriesId, lang);
|
||||||
|
|
||||||
|
const worldsMap: Map<string, SeriesWorldListProps> = new Map();
|
||||||
|
|
||||||
|
for (const row of worldsResult) {
|
||||||
|
if (!worldsMap.has(row.world_id)) {
|
||||||
|
worldsMap.set(row.world_id, {
|
||||||
|
id: row.world_id,
|
||||||
|
name: row.world_name ? System.decryptDataWithUserKey(row.world_name, userKey) : '',
|
||||||
|
history: row.history ? System.decryptDataWithUserKey(row.history, userKey) : '',
|
||||||
|
politics: row.politics ? System.decryptDataWithUserKey(row.politics, userKey) : '',
|
||||||
|
economy: row.economy ? System.decryptDataWithUserKey(row.economy, userKey) : '',
|
||||||
|
religion: row.religion ? System.decryptDataWithUserKey(row.religion, userKey) : '',
|
||||||
|
languages: row.languages ? System.decryptDataWithUserKey(row.languages, userKey) : '',
|
||||||
|
laws: [],
|
||||||
|
biomes: [],
|
||||||
|
issues: [],
|
||||||
|
customs: [],
|
||||||
|
kingdoms: [],
|
||||||
|
climate: [],
|
||||||
|
resources: [],
|
||||||
|
wildlife: [],
|
||||||
|
arts: [],
|
||||||
|
ethnicGroups: [],
|
||||||
|
socialClasses: [],
|
||||||
|
importantCharacters: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.element_id) {
|
||||||
|
const world = worldsMap.get(row.world_id)!;
|
||||||
|
const element: SeriesWorldElementProps = {
|
||||||
|
id: row.element_id,
|
||||||
|
name: row.element_name ? System.decryptDataWithUserKey(row.element_name, userKey) : '',
|
||||||
|
description: row.element_description ? System.decryptDataWithUserKey(row.element_description, userKey) : ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const key = ELEMENT_TYPE_MAP[row.element_type];
|
||||||
|
if (key && Array.isArray(world[key])) {
|
||||||
|
(world[key] as SeriesWorldElementProps[]).push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(worldsMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new world to a series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param name - The name of the world
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The new world ID
|
||||||
|
*/
|
||||||
|
public static addWorld(userId: string, seriesId: string, name: string, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
const hashedName: string = System.hashElement(name);
|
||||||
|
|
||||||
|
const exists: boolean = SeriesWorldRepo.checkWorldExist(userId, seriesId, hashedName, lang);
|
||||||
|
if (exists) {
|
||||||
|
throw new Error(lang === 'fr' ? 'Un monde avec ce nom existe déjà.' : 'A world with this name already exists.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const worldId: string = System.createUniqueId();
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
|
||||||
|
SeriesWorldRepo.insertNewWorld(worldId, userId, seriesId, encryptedName, hashedName, lang);
|
||||||
|
return worldId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a world's information.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param worldId - The unique identifier of the world
|
||||||
|
* @param world - The updated world data
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if successful
|
||||||
|
*/
|
||||||
|
public static updateWorld(userId: string, worldId: string, world: SeriesWorldUpdateProps, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(world.name, userKey);
|
||||||
|
const hashedName: string = System.hashElement(world.name);
|
||||||
|
const encryptedHistory: string | null = world.history ? System.encryptDataWithUserKey(world.history, userKey) : null;
|
||||||
|
const encryptedPolitics: string | null = world.politics ? System.encryptDataWithUserKey(world.politics, userKey) : null;
|
||||||
|
const encryptedEconomy: string | null = world.economy ? System.encryptDataWithUserKey(world.economy, userKey) : null;
|
||||||
|
const encryptedReligion: string | null = world.religion ? System.encryptDataWithUserKey(world.religion, userKey) : null;
|
||||||
|
const encryptedLanguages: string | null = world.languages ? System.encryptDataWithUserKey(world.languages, userKey) : null;
|
||||||
|
|
||||||
|
return SeriesWorldRepo.updateWorld(userId, worldId, encryptedName, hashedName, encryptedHistory, encryptedPolitics, encryptedEconomy, encryptedReligion, encryptedLanguages, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new element to a world.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param worldId - The unique identifier of the world
|
||||||
|
* @param elementType - The type of element (0-11)
|
||||||
|
* @param name - The name of the element
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @param description - The description of the element (optional)
|
||||||
|
* @returns The new element ID
|
||||||
|
*/
|
||||||
|
public static addElement(userId: string, worldId: string, elementType: number, name: string, lang: 'fr' | 'en' = 'fr', description?: string): string {
|
||||||
|
const userKey: string = getUserEncryptionKey(userId);
|
||||||
|
const elementId: string = System.createUniqueId();
|
||||||
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
|
const originalName: string = System.hashElement(name);
|
||||||
|
const encryptedDescription: string | null = description ? System.encryptDataWithUserKey(description, userKey) : null;
|
||||||
|
|
||||||
|
SeriesWorldRepo.insertElement(elementId, worldId, userId, elementType, encryptedName, originalName, encryptedDescription, lang);
|
||||||
|
return elementId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an element from a world.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param elementId - The unique identifier of the element
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if successful
|
||||||
|
*/
|
||||||
|
public static deleteElement(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
return SeriesWorldRepo.deleteElement(userId, elementId, lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ export interface SpellProps {
|
|||||||
components: string | null;
|
components: string | null;
|
||||||
limitations: string | null;
|
limitations: string | null;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
|
seriesSpellId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpellListItem {
|
export interface SpellListItem {
|
||||||
@@ -27,6 +28,7 @@ export interface SpellListItem {
|
|||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
tags: SpellTagProps[];
|
tags: SpellTagProps[];
|
||||||
|
seriesSpellId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpellListResponse {
|
export interface SpellListResponse {
|
||||||
@@ -193,6 +195,7 @@ export default class Spell {
|
|||||||
name: decryptedName,
|
name: decryptedName,
|
||||||
description: truncatedDescription,
|
description: truncatedDescription,
|
||||||
tags: resolvedTags,
|
tags: resolvedTags,
|
||||||
|
seriesSpellId: spell.series_spell_id || null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -240,6 +243,7 @@ export default class Spell {
|
|||||||
components: spell.components ? System.decryptDataWithUserKey(spell.components, userKey) : null,
|
components: spell.components ? System.decryptDataWithUserKey(spell.components, userKey) : null,
|
||||||
limitations: spell.limitations ? System.decryptDataWithUserKey(spell.limitations, userKey) : null,
|
limitations: spell.limitations ? System.decryptDataWithUserKey(spell.limitations, userKey) : null,
|
||||||
notes: spell.notes ? System.decryptDataWithUserKey(spell.notes, userKey) : null,
|
notes: spell.notes ? System.decryptDataWithUserKey(spell.notes, userKey) : null,
|
||||||
|
seriesSpellId: spell.series_spell_id || null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +263,7 @@ export default class Spell {
|
|||||||
* @param lang - The language for error messages ('fr' or 'en')
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
* @returns The created spell props
|
* @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 {
|
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 userKey: string = getUserEncryptionKey(userId);
|
||||||
const spellId: string = existingSpellId || System.createUniqueId();
|
const spellId: string = existingSpellId || System.createUniqueId();
|
||||||
|
|
||||||
@@ -287,6 +291,7 @@ export default class Spell {
|
|||||||
encryptedLimitations,
|
encryptedLimitations,
|
||||||
encryptedNotes,
|
encryptedNotes,
|
||||||
lang,
|
lang,
|
||||||
|
seriesSpellId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -299,6 +304,7 @@ export default class Spell {
|
|||||||
components,
|
components,
|
||||||
limitations,
|
limitations,
|
||||||
notes,
|
notes,
|
||||||
|
seriesSpellId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +323,7 @@ export default class Spell {
|
|||||||
* @param lang - The language for error messages ('fr' or 'en')
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
* @returns True if the update was successful
|
* @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 {
|
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 userKey: string = getUserEncryptionKey(userId);
|
||||||
|
|
||||||
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
|
||||||
@@ -343,6 +349,7 @@ export default class Spell {
|
|||||||
encryptedLimitations,
|
encryptedLimitations,
|
||||||
encryptedNotes,
|
encryptedNotes,
|
||||||
lang,
|
lang,
|
||||||
|
seriesSpellId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1068,7 +1068,11 @@ export default class Sync {
|
|||||||
|
|
||||||
const bookToolsQuery: SyncedBookToolsResult | null = BookRepo.fetchSyncedBookTools(userId, currentBookId, lang);
|
const bookToolsQuery: SyncedBookToolsResult | null = BookRepo.fetchSyncedBookTools(userId, currentBookId, lang);
|
||||||
const bookTools: SyncedBookTools | null = bookToolsQuery ? {
|
const bookTools: SyncedBookTools | null = bookToolsQuery ? {
|
||||||
lastUpdate: bookToolsQuery.last_update
|
lastUpdate: bookToolsQuery.last_update,
|
||||||
|
charactersEnabled: bookToolsQuery.characters_enabled === 1,
|
||||||
|
worldsEnabled: bookToolsQuery.worlds_enabled === 1,
|
||||||
|
locationsEnabled: bookToolsQuery.locations_enabled === 1,
|
||||||
|
spellsEnabled: bookToolsQuery.spells_enabled === 1
|
||||||
} : null;
|
} : null;
|
||||||
|
|
||||||
const bookSpells: SyncedSpell[] = allSpells
|
const bookSpells: SyncedSpell[] = allSpells
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export interface WorldProps {
|
|||||||
ethnicGroups: WorldElement[];
|
ethnicGroups: WorldElement[];
|
||||||
socialClasses: WorldElement[];
|
socialClasses: WorldElement[];
|
||||||
importantCharacters: WorldElement[];
|
importantCharacters: WorldElement[];
|
||||||
|
seriesWorldId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorldListResponse {
|
export interface WorldListResponse {
|
||||||
@@ -97,7 +98,7 @@ export default class World {
|
|||||||
* @returns The unique identifier of the newly created world
|
* @returns The unique identifier of the newly created world
|
||||||
* @throws Error if a world with the same name already exists for this book
|
* @throws Error if a world with the same name already exists for this book
|
||||||
*/
|
*/
|
||||||
public static addNewWorld(userId: string, bookId: string, worldName: string, lang: 'fr' | 'en' = 'fr', existingWorldId?: string): string {
|
public static addNewWorld(userId: string, bookId: string, worldName: string, lang: 'fr' | 'en' = 'fr', existingWorldId?: string, seriesWorldId: string | null = null): string {
|
||||||
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
||||||
const hashedWorldName: string = System.hashElement(worldName);
|
const hashedWorldName: string = System.hashElement(worldName);
|
||||||
if (!existingWorldId && WorldRepository.checkWorldExist(userId, bookId, hashedWorldName, lang)) {
|
if (!existingWorldId && WorldRepository.checkWorldExist(userId, bookId, hashedWorldName, lang)) {
|
||||||
@@ -105,7 +106,7 @@ export default class World {
|
|||||||
}
|
}
|
||||||
const encryptedWorldName: string = System.encryptDataWithUserKey(worldName, userEncryptionKey);
|
const encryptedWorldName: string = System.encryptDataWithUserKey(worldName, userEncryptionKey);
|
||||||
const worldId: string = existingWorldId || System.createUniqueId();
|
const worldId: string = existingWorldId || System.createUniqueId();
|
||||||
return WorldRepository.insertNewWorld(worldId, userId, bookId, encryptedWorldName, hashedWorldName, lang);
|
return WorldRepository.insertNewWorld(worldId, userId, bookId, encryptedWorldName, hashedWorldName, lang, seriesWorldId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,6 +148,7 @@ export default class World {
|
|||||||
ethnicGroups: [],
|
ethnicGroups: [],
|
||||||
socialClasses: [],
|
socialClasses: [],
|
||||||
importantCharacters: [],
|
importantCharacters: [],
|
||||||
|
seriesWorldId: queryRow.series_world_id || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
worlds.push(newWorld);
|
worlds.push(newWorld);
|
||||||
@@ -228,7 +230,7 @@ export default class World {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
WorldRepository.updateWorld(userId, world.id, encryptedName, System.hashElement(world.name), encryptedHistory, encryptedPolitics, encryptedEconomy, encryptedReligion, encryptedLanguages, System.timeStampInSeconds(), lang);
|
WorldRepository.updateWorld(userId, world.id, encryptedName, System.hashElement(world.name), encryptedHistory, encryptedPolitics, encryptedEconomy, encryptedReligion, encryptedLanguages, System.timeStampInSeconds(), lang, world.seriesWorldId || null);
|
||||||
return WorldRepository.updateWorldElements(userId, elementsToUpdate, lang);
|
return WorldRepository.updateWorldElements(userId, elementsToUpdate, lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ export interface BookToolsTable extends Record<string, SQLiteValue> {
|
|||||||
|
|
||||||
export interface SyncedBookToolsResult extends Record<string, SQLiteValue> {
|
export interface SyncedBookToolsResult extends Record<string, SQLiteValue> {
|
||||||
last_update: number;
|
last_update: number;
|
||||||
|
characters_enabled: number;
|
||||||
|
worlds_enabled: number;
|
||||||
|
locations_enabled: number;
|
||||||
|
spells_enabled: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class BookRepo {
|
export default class BookRepo {
|
||||||
@@ -440,7 +444,7 @@ export default class BookRepo {
|
|||||||
static fetchSyncedBookTools(userId: string, bookId: string, lang: 'fr' | 'en'): SyncedBookToolsResult | null {
|
static fetchSyncedBookTools(userId: string, bookId: string, lang: 'fr' | 'en'): SyncedBookToolsResult | null {
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = 'SELECT last_update FROM book_tools WHERE user_id = ? AND book_id = ?';
|
const query: string = 'SELECT last_update, characters_enabled, worlds_enabled, locations_enabled, spells_enabled FROM book_tools WHERE user_id = ? AND book_id = ?';
|
||||||
const params: SQLiteValue[] = [userId, bookId];
|
const params: SQLiteValue[] = [userId, bookId];
|
||||||
const result = db.get(query, params) as SyncedBookToolsResult | undefined;
|
const result = db.get(query, params) as SyncedBookToolsResult | undefined;
|
||||||
return result ?? null;
|
return result ?? null;
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ export default class ChapterContentRepository {
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
`;
|
`;
|
||||||
const params: SQLiteValue[] = [userId, bookId];
|
const params: SQLiteValue[] = [userId, bookId];
|
||||||
const chapterContents: ChapterContentQueryResult[] = db.all(query, params) as ChapterContentQueryResult[];
|
return db.all(query, params) as ChapterContentQueryResult[];
|
||||||
return chapterContents;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(`DB Error: ${error.message}`);
|
console.error(`DB Error: ${error.message}`);
|
||||||
@@ -127,8 +126,7 @@ export default class ChapterContentRepository {
|
|||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = 'SELECT version, content, words_count FROM book_chapter_content WHERE author_id=? AND chapter_id=? AND version=?';
|
const query: string = 'SELECT version, content, words_count FROM book_chapter_content WHERE author_id=? AND chapter_id=? AND version=?';
|
||||||
const params: SQLiteValue[] = [userId, chapterId, version];
|
const params: SQLiteValue[] = [userId, chapterId, version];
|
||||||
const companionContents: CompanionContentQueryResult[] = db.all(query, params) as CompanionContentQueryResult[];
|
return db.all(query, params) as CompanionContentQueryResult[];
|
||||||
return companionContents;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(`DB Error: ${error.message}`);
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
|||||||
@@ -54,23 +54,24 @@ export interface CharacterResult extends Record<string, SQLiteValue> {
|
|||||||
character_id: string;
|
character_id: string;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
nickname: string;
|
nickname: string | null;
|
||||||
age: string;
|
age: string | null;
|
||||||
gender: string;
|
gender: string | null;
|
||||||
species: string;
|
species: string | null;
|
||||||
nationality: string;
|
nationality: string | null;
|
||||||
status: string;
|
status: string | null;
|
||||||
title: string;
|
title: string;
|
||||||
category: string;
|
category: string;
|
||||||
image: string;
|
image: string;
|
||||||
role: string;
|
role: string;
|
||||||
biography: string;
|
biography: string;
|
||||||
history: string;
|
history: string;
|
||||||
speech_pattern: string;
|
speech_pattern: string | null;
|
||||||
catchphrase: string;
|
catchphrase: string | null;
|
||||||
residence: string;
|
residence: string | null;
|
||||||
notes: string;
|
notes: string | null;
|
||||||
color: string;
|
color: string | null;
|
||||||
|
series_character_id: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttributeResult extends Record<string, SQLiteValue> {
|
export interface AttributeResult extends Record<string, SQLiteValue> {
|
||||||
@@ -83,11 +84,22 @@ export interface CompleteCharacterResult extends Record<string, SQLiteValue> {
|
|||||||
character_id: string;
|
character_id: string;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
|
nickname: string | null;
|
||||||
|
age: string | null;
|
||||||
|
gender: string | null;
|
||||||
|
species: string | null;
|
||||||
|
nationality: string | null;
|
||||||
|
status: string | null;
|
||||||
category: string;
|
category: string;
|
||||||
title: string;
|
title: string;
|
||||||
role: string;
|
role: string;
|
||||||
biography: string;
|
biography: string;
|
||||||
history: string;
|
history: string;
|
||||||
|
speech_pattern: string | null;
|
||||||
|
catchphrase: string | null;
|
||||||
|
residence: string | null;
|
||||||
|
notes: string | null;
|
||||||
|
color: string | null;
|
||||||
attribute_name: string;
|
attribute_name: string;
|
||||||
attribute_value: string;
|
attribute_value: string;
|
||||||
}
|
}
|
||||||
@@ -103,7 +115,7 @@ export default class CharacterRepo {
|
|||||||
public static fetchCharacters(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): CharacterResult[] {
|
public static fetchCharacters(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): CharacterResult[] {
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
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 book_characters WHERE book_id=? AND user_id=?';
|
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, series_character_id FROM book_characters WHERE book_id=? AND user_id=?';
|
||||||
const params: SQLiteValue[] = [bookId, userId];
|
const params: SQLiteValue[] = [bookId, userId];
|
||||||
const characters: CharacterResult[] = db.all(query, params) as CharacterResult[];
|
const characters: CharacterResult[] = db.all(query, params) as CharacterResult[];
|
||||||
return characters;
|
return characters;
|
||||||
@@ -129,46 +141,35 @@ export default class CharacterRepo {
|
|||||||
*/
|
*/
|
||||||
public static addNewCharacter(userId: string, characterId: string, characterData: {
|
public static addNewCharacter(userId: string, characterId: string, characterData: {
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string | null;
|
||||||
nickname: string;
|
nickname: string | null;
|
||||||
age: string;
|
age: string | null;
|
||||||
gender: string;
|
gender: string | null;
|
||||||
species: string;
|
species: string | null;
|
||||||
nationality: string;
|
nationality: string | null;
|
||||||
status: string;
|
status: string | null;
|
||||||
title: string;
|
title: string | null;
|
||||||
category: string;
|
category: string | null;
|
||||||
image: string;
|
image: string | null;
|
||||||
role: string;
|
role: string | null;
|
||||||
biography: string;
|
biography: string | null;
|
||||||
history: string;
|
history: string | null;
|
||||||
speechPattern: string;
|
speechPattern: string | null;
|
||||||
catchphrase: string;
|
catchphrase: string | null;
|
||||||
residence: string;
|
residence: string | null;
|
||||||
notes: string;
|
notes: string | null;
|
||||||
color: string;
|
color: string | null;
|
||||||
}, bookId: string, lang: 'fr' | 'en' = 'fr'): string {
|
}, bookId: string, lang: 'fr' | 'en' = 'fr', seriesCharacterId: string | null = null): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = `INSERT INTO book_characters (
|
const query: string = seriesCharacterId
|
||||||
character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status,
|
? 'INSERT INTO book_characters (character_id, book_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, series_character_id, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'
|
||||||
category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update
|
: 'INSERT INTO book_characters (character_id, book_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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)';
|
||||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
|
const params: SQLiteValue[] = seriesCharacterId
|
||||||
const params: SQLiteValue[] = [
|
? [characterId, bookId, userId, characterData.firstName, characterData.lastName, characterData.nickname, characterData.age, characterData.gender, characterData.species, characterData.nationality, characterData.status, characterData.category, characterData.title, characterData.image, characterData.role, characterData.biography, characterData.history, characterData.speechPattern, characterData.catchphrase, characterData.residence, characterData.notes, characterData.color, seriesCharacterId, System.timeStampInSeconds()]
|
||||||
characterId, bookId, userId,
|
: [characterId, bookId, userId, characterData.firstName, characterData.lastName, characterData.nickname, characterData.age, characterData.gender, characterData.species, characterData.nationality, characterData.status, characterData.category, characterData.title, characterData.image, characterData.role, characterData.biography, characterData.history, characterData.speechPattern, characterData.catchphrase, characterData.residence, characterData.notes, characterData.color, System.timeStampInSeconds()];
|
||||||
characterData.firstName, characterData.lastName, characterData.nickname,
|
insertResult = db.run(query, params);
|
||||||
characterData.age, characterData.gender, characterData.species,
|
|
||||||
characterData.nationality, characterData.status, characterData.category,
|
|
||||||
characterData.title, characterData.image, characterData.role,
|
|
||||||
characterData.biography, characterData.history, characterData.speechPattern,
|
|
||||||
characterData.catchphrase, characterData.residence, characterData.notes,
|
|
||||||
characterData.color, System.timeStampInSeconds()
|
|
||||||
];
|
|
||||||
const insertResult: RunResult = db.run(query, params);
|
|
||||||
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;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(`DB Error: ${error.message}`);
|
console.error(`DB Error: ${error.message}`);
|
||||||
@@ -178,6 +179,10 @@ export default class CharacterRepo {
|
|||||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,15 +196,12 @@ export default class CharacterRepo {
|
|||||||
* @returns The attribute ID if successful
|
* @returns The attribute ID if successful
|
||||||
*/
|
*/
|
||||||
static insertAttribute(attributeId: string, characterId: string, userId: string, type: string, name: string, lang: 'fr' | 'en' = 'fr'): string {
|
static insertAttribute(attributeId: string, characterId: string, userId: string, type: string, name: string, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = 'INSERT INTO `book_characters_attributes` (attr_id, character_id, user_id, attribute_name, attribute_value, last_update) VALUES (?,?,?,?,?,?)';
|
const query: string = 'INSERT INTO book_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()];
|
const params: SQLiteValue[] = [attributeId, characterId, userId, type, name, System.timeStampInSeconds()];
|
||||||
const insertResult: RunResult = db.run(query, params);
|
insertResult = db.run(query, params);
|
||||||
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;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(`DB Error: ${error.message}`);
|
console.error(`DB Error: ${error.message}`);
|
||||||
@@ -209,6 +211,10 @@ export default class CharacterRepo {
|
|||||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -222,41 +228,33 @@ export default class CharacterRepo {
|
|||||||
*/
|
*/
|
||||||
static updateCharacter(userId: string, id: string, characterData: {
|
static updateCharacter(userId: string, id: string, characterData: {
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string | null;
|
||||||
nickname: string;
|
nickname: string | null;
|
||||||
age: string;
|
age: string | null;
|
||||||
gender: string;
|
gender: string | null;
|
||||||
species: string;
|
species: string | null;
|
||||||
nationality: string;
|
nationality: string | null;
|
||||||
status: string;
|
status: string | null;
|
||||||
title: string;
|
title: string | null;
|
||||||
category: string;
|
category: string | null;
|
||||||
image: string;
|
image: string | null;
|
||||||
role: string;
|
role: string | null;
|
||||||
biography: string;
|
biography: string | null;
|
||||||
history: string;
|
history: string | null;
|
||||||
speechPattern: string;
|
speechPattern: string | null;
|
||||||
catchphrase: string;
|
catchphrase: string | null;
|
||||||
residence: string;
|
residence: string | null;
|
||||||
notes: string;
|
notes: string | null;
|
||||||
color: string;
|
color: string | null;
|
||||||
}, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
}, lastUpdate: number, lang: 'fr' | 'en' = 'fr', seriesCharacterId: string | null = null): boolean {
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = `UPDATE book_characters SET
|
const query: string = seriesCharacterId !== null
|
||||||
first_name=?, last_name=?, nickname=?, age=?, gender=?, species=?, nationality=?, status=?,
|
? 'UPDATE book_characters SET first_name=?, last_name=?, nickname=?, age=?, gender=?, species=?, nationality=?, status=?, title=?, category=?, image=?, role=?, biography=?, history=?, speech_pattern=?, catchphrase=?, residence=?, notes=?, color=?, series_character_id=?, last_update=? WHERE character_id=? AND user_id=?'
|
||||||
title=?, category=?, image=?, role=?, biography=?, history=?,
|
: 'UPDATE book_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=?';
|
||||||
speech_pattern=?, catchphrase=?, residence=?, notes=?, color=?, last_update=?
|
const params: SQLiteValue[] = seriesCharacterId !== null
|
||||||
WHERE character_id=? AND user_id=?`;
|
? [characterData.firstName, characterData.lastName, characterData.nickname, characterData.age, characterData.gender, characterData.species, characterData.nationality, characterData.status, characterData.title, characterData.category, characterData.image, characterData.role, characterData.biography, characterData.history, characterData.speechPattern, characterData.catchphrase, characterData.residence, characterData.notes, characterData.color, seriesCharacterId, lastUpdate, id, userId]
|
||||||
const params: SQLiteValue[] = [
|
: [characterData.firstName, characterData.lastName, characterData.nickname, characterData.age, characterData.gender, characterData.species, characterData.nationality, characterData.status, characterData.title, characterData.category, characterData.image, characterData.role, characterData.biography, characterData.history, characterData.speechPattern, characterData.catchphrase, characterData.residence, characterData.notes, characterData.color, lastUpdate, id, userId];
|
||||||
characterData.firstName, characterData.lastName, characterData.nickname,
|
|
||||||
characterData.age, characterData.gender, characterData.species,
|
|
||||||
characterData.nationality, characterData.status, characterData.title,
|
|
||||||
characterData.category, characterData.image, characterData.role,
|
|
||||||
characterData.biography, characterData.history, characterData.speechPattern,
|
|
||||||
characterData.catchphrase, characterData.residence, characterData.notes,
|
|
||||||
characterData.color, lastUpdate, id, userId
|
|
||||||
];
|
|
||||||
const updateResult: RunResult = db.run(query, params);
|
const updateResult: RunResult = db.run(query, params);
|
||||||
return updateResult.changes > 0;
|
return updateResult.changes > 0;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@@ -357,7 +355,7 @@ export default class CharacterRepo {
|
|||||||
static fetchCompleteCharacters(userId: string, bookId: string, tags: string[], lang: 'fr' | 'en' = 'fr'): CompleteCharacterResult[] {
|
static fetchCompleteCharacters(userId: string, bookId: string, tags: string[], lang: 'fr' | 'en' = 'fr'): CompleteCharacterResult[] {
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
let query: string = 'SELECT charac.character_id, first_name, last_name, category, title, role, biography, history, attribute_name, attribute_value FROM book_characters AS charac LEFT JOIN book_characters_attributes AS attr ON charac.character_id=attr.character_id WHERE charac.user_id=? AND charac.book_id=?';
|
let query: string = 'SELECT charac.character_id, first_name, last_name, nickname, age, gender, species, nationality, status, category, title, role, biography, history, speech_pattern, catchphrase, residence, notes, color, attribute_name, attribute_value FROM book_characters AS charac LEFT JOIN book_characters_attributes AS attr ON charac.character_id=attr.character_id WHERE charac.user_id=? AND charac.book_id=?';
|
||||||
let params: SQLiteValue[] = [userId, bookId];
|
let params: SQLiteValue[] = [userId, bookId];
|
||||||
if (tags && tags.length > 0) {
|
if (tags && tags.length > 0) {
|
||||||
const placeholders: string = tags.map((): string => '?').join(',');
|
const placeholders: string = tags.map((): string => '?').join(',');
|
||||||
@@ -393,7 +391,7 @@ export default class CharacterRepo {
|
|||||||
static updateCharacterAttribute(userId: string, characterAttributeId: string, attributeName: string, attributeValue: string, lastUpdate: number, lang: "fr" | "en"): boolean {
|
static updateCharacterAttribute(userId: string, characterAttributeId: string, attributeName: string, attributeValue: string, lastUpdate: number, lang: "fr" | "en"): boolean {
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = 'UPDATE `book_characters_attributes` SET `attribute_name`=?,`attribute_value`=?, last_update=FROM_UNIXTIME(?) WHERE `attr_id`=UUID_TO_BIN(?) AND `user_id`=UUID_TO_BIN(?)';
|
const query: string = 'UPDATE book_characters_attributes SET attribute_name=?, attribute_value=?, last_update=? WHERE attr_id=? AND user_id=?';
|
||||||
const params: SQLiteValue[] = [attributeName, attributeValue, lastUpdate, characterAttributeId, userId];
|
const params: SQLiteValue[] = [attributeName, attributeValue, lastUpdate, characterAttributeId, userId];
|
||||||
const updateResult: RunResult = db.run(query, params);
|
const updateResult: RunResult = db.run(query, params);
|
||||||
return updateResult.changes > 0;
|
return updateResult.changes > 0;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface LocationQueryResult extends Record<string, SQLiteValue> {
|
|||||||
sub_element_id: string;
|
sub_element_id: string;
|
||||||
sub_elem_name: string;
|
sub_elem_name: string;
|
||||||
sub_elem_description: string;
|
sub_elem_description: string;
|
||||||
|
series_location_id: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LocationElementQueryResult extends Record<string, SQLiteValue> {
|
export interface LocationElementQueryResult extends Record<string, SQLiteValue> {
|
||||||
@@ -89,15 +90,7 @@ export default class LocationRepo {
|
|||||||
static getLocation(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): LocationQueryResult[] {
|
static getLocation(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): LocationQueryResult[] {
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = `
|
const query: string = 'SELECT loc_id, loc_name, element.element_id AS element_id, element.element_name, element.element_description, sub_elem.sub_element_id AS sub_element_id, sub_elem.sub_elem_name, sub_elem.sub_elem_description, location.series_location_id FROM book_location AS location LEFT JOIN location_element AS element ON location.loc_id = element.location LEFT JOIN location_sub_element AS sub_elem ON element.element_id = sub_elem.element_id WHERE location.user_id = ? AND location.book_id = ?';
|
||||||
SELECT loc_id, loc_name, element.element_id AS element_id, element.element_name,
|
|
||||||
element.element_description, sub_elem.sub_element_id AS sub_element_id,
|
|
||||||
sub_elem.sub_elem_name, sub_elem.sub_elem_description
|
|
||||||
FROM book_location AS location
|
|
||||||
LEFT JOIN location_element AS element ON location.loc_id = element.location
|
|
||||||
LEFT JOIN location_sub_element AS sub_elem ON element.element_id = sub_elem.element_id
|
|
||||||
WHERE location.user_id = ? AND location.book_id = ?
|
|
||||||
`;
|
|
||||||
const params: SQLiteValue[] = [userId, bookId];
|
const params: SQLiteValue[] = [userId, bookId];
|
||||||
const locations: LocationQueryResult[] = db.all(query, params) as LocationQueryResult[];
|
const locations: LocationQueryResult[] = db.all(query, params) as LocationQueryResult[];
|
||||||
return locations;
|
return locations;
|
||||||
@@ -122,19 +115,17 @@ export default class LocationRepo {
|
|||||||
* @param lang - The language for error messages ('fr' or 'en')
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
* @returns The location ID if insertion was successful
|
* @returns The location ID if insertion was successful
|
||||||
*/
|
*/
|
||||||
static insertLocation(userId: string, locationId: string, bookId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): string {
|
static insertLocation(userId: string, locationId: string, bookId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr', seriesLocationId: string | null = null): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = `
|
const query: string = seriesLocationId
|
||||||
INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, last_update)
|
? 'INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, series_location_id, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
: 'INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, last_update) VALUES (?, ?, ?, ?, ?, ?)';
|
||||||
`;
|
const params: SQLiteValue[] = seriesLocationId
|
||||||
const params: SQLiteValue[] = [locationId, bookId, userId, encryptedName, originalName, System.timeStampInSeconds()];
|
? [locationId, bookId, userId, encryptedName, originalName, seriesLocationId, System.timeStampInSeconds()]
|
||||||
const insertResult: RunResult = db.run(query, params);
|
: [locationId, bookId, userId, encryptedName, originalName, System.timeStampInSeconds()];
|
||||||
if (!insertResult || insertResult.changes === 0) {
|
insertResult = db.run(query, params);
|
||||||
throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout de la section d'emplacement.` : `Error adding location section.`);
|
|
||||||
}
|
|
||||||
return locationId;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(`DB Error: ${error.message}`);
|
console.error(`DB Error: ${error.message}`);
|
||||||
@@ -144,6 +135,10 @@ export default class LocationRepo {
|
|||||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "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 la section d'emplacement.` : `Error adding location section.`);
|
||||||
|
}
|
||||||
|
return locationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,18 +152,12 @@ export default class LocationRepo {
|
|||||||
* @returns The element ID if insertion was successful
|
* @returns The element ID if insertion was successful
|
||||||
*/
|
*/
|
||||||
static insertLocationElement(userId: string, elementId: string, locationId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): string {
|
static insertLocationElement(userId: string, elementId: string, locationId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = `
|
const query: string = 'INSERT INTO location_element (element_id, location, user_id, element_name, original_name, element_description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)';
|
||||||
INSERT INTO location_element (element_id, location, user_id, element_name, original_name, element_description, last_update)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`;
|
|
||||||
const params: SQLiteValue[] = [elementId, locationId, userId, encryptedName, originalName, '', System.timeStampInSeconds()];
|
const params: SQLiteValue[] = [elementId, locationId, userId, encryptedName, originalName, '', System.timeStampInSeconds()];
|
||||||
const insertResult: RunResult = db.run(query, params);
|
insertResult = db.run(query, params);
|
||||||
if (!insertResult || insertResult.changes === 0) {
|
|
||||||
throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout de l'élément d'emplacement.` : `Error adding location element.`);
|
|
||||||
}
|
|
||||||
return elementId;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(`DB Error: ${error.message}`);
|
console.error(`DB Error: ${error.message}`);
|
||||||
@@ -178,6 +167,10 @@ export default class LocationRepo {
|
|||||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "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'élément d'emplacement.` : `Error adding location element.`);
|
||||||
|
}
|
||||||
|
return elementId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,18 +184,12 @@ export default class LocationRepo {
|
|||||||
* @returns The sub-element ID if insertion was successful
|
* @returns The sub-element ID if insertion was successful
|
||||||
*/
|
*/
|
||||||
static insertLocationSubElement(userId: string, subElementId: string, elementId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): string {
|
static insertLocationSubElement(userId: string, subElementId: string, elementId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = `
|
const query: string = 'INSERT INTO location_sub_element (sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)';
|
||||||
INSERT INTO location_sub_element (sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`;
|
|
||||||
const params: SQLiteValue[] = [subElementId, elementId, userId, encryptedName, originalName, '', System.timeStampInSeconds()];
|
const params: SQLiteValue[] = [subElementId, elementId, userId, encryptedName, originalName, '', System.timeStampInSeconds()];
|
||||||
const insertResult: RunResult = db.run(query, params);
|
insertResult = db.run(query, params);
|
||||||
if (!insertResult || insertResult.changes === 0) {
|
|
||||||
throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du sous-élément d'emplacement.` : `Error adding location sub-element.`);
|
|
||||||
}
|
|
||||||
return subElementId;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(`DB Error: ${error.message}`);
|
console.error(`DB Error: ${error.message}`);
|
||||||
@@ -212,6 +199,10 @@ export default class LocationRepo {
|
|||||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "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 sous-élément d'emplacement.` : `Error adding location sub-element.`);
|
||||||
|
}
|
||||||
|
return subElementId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -864,4 +855,46 @@ export default class LocationRepo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a location section with optional name change and series link.
|
||||||
|
* @param userId - The user's unique identifier
|
||||||
|
* @param sectionId - The section's unique identifier
|
||||||
|
* @param encryptedName - The new encrypted name (optional)
|
||||||
|
* @param originalName - The new original name (optional)
|
||||||
|
* @param seriesLocationId - The series location ID to link (optional, null to unlink)
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the update was successful
|
||||||
|
*/
|
||||||
|
static updateSectionWithSeriesLink(userId: string, sectionId: string, encryptedName: string | null, originalName: string | null, seriesLocationId: string | null, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const setClauses: string[] = ['last_update=' + System.timeStampInSeconds()];
|
||||||
|
const params: SQLiteValue[] = [];
|
||||||
|
|
||||||
|
if (encryptedName !== null && originalName !== null) {
|
||||||
|
setClauses.push('loc_name=?', 'loc_original_name=?');
|
||||||
|
params.push(encryptedName, originalName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seriesLocationId !== undefined) {
|
||||||
|
setClauses.push('series_location_id=?');
|
||||||
|
params.push(seriesLocationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
params.push(sectionId, userId);
|
||||||
|
|
||||||
|
const query: string = 'UPDATE book_location SET ' + setClauses.join(', ') + ' WHERE loc_id=? AND user_id=?';
|
||||||
|
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 la section d'emplacement.` : `Unable to update location section.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
472
electron/database/repositories/series-character.repo.ts
Normal file
472
electron/database/repositories/series-character.repo.ts
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
import {Database, QueryResult, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
|
||||||
|
import System from "../System.js";
|
||||||
|
|
||||||
|
export interface SeriesCharacterResult extends Record<string, SQLiteValue> {
|
||||||
|
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<string, SQLiteValue> {
|
||||||
|
attr_id: string;
|
||||||
|
attribute_name: string;
|
||||||
|
attribute_value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesCharactersTableResult extends Record<string, SQLiteValue> {
|
||||||
|
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<string, SQLiteValue> {
|
||||||
|
attr_id: string;
|
||||||
|
character_id: string;
|
||||||
|
user_id: string;
|
||||||
|
attribute_name: string;
|
||||||
|
attribute_value: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesCharacterResult extends Record<string, SQLiteValue> {
|
||||||
|
character_id: string;
|
||||||
|
series_id: string;
|
||||||
|
first_name: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesCharacterAttributeResult extends Record<string, SQLiteValue> {
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
623
electron/database/repositories/series-location.repo.ts
Normal file
623
electron/database/repositories/series-location.repo.ts
Normal file
@@ -0,0 +1,623 @@
|
|||||||
|
import { Database, QueryResult, RunResult, SQLiteValue } from 'node-sqlite3-wasm';
|
||||||
|
import System from "../System.js";
|
||||||
|
|
||||||
|
export interface SeriesLocationResult extends Record<string, SQLiteValue> {
|
||||||
|
loc_id: string;
|
||||||
|
loc_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesLocationElementResult extends Record<string, SQLiteValue> {
|
||||||
|
element_id: string;
|
||||||
|
location_id: string;
|
||||||
|
element_name: string;
|
||||||
|
element_description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesLocationSubElementResult extends Record<string, SQLiteValue> {
|
||||||
|
sub_element_id: string;
|
||||||
|
element_id: string;
|
||||||
|
sub_elem_name: string;
|
||||||
|
sub_elem_description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesLocationsTableResult extends Record<string, SQLiteValue> {
|
||||||
|
loc_id: string;
|
||||||
|
series_id: string;
|
||||||
|
user_id: string;
|
||||||
|
loc_name: string;
|
||||||
|
loc_original_name: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesLocationElementsTableResult extends Record<string, SQLiteValue> {
|
||||||
|
element_id: string;
|
||||||
|
location_id: string;
|
||||||
|
user_id: string;
|
||||||
|
element_name: string;
|
||||||
|
original_name: string;
|
||||||
|
element_description: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesLocationSubElementsTableResult extends Record<string, SQLiteValue> {
|
||||||
|
sub_element_id: string;
|
||||||
|
element_id: string;
|
||||||
|
user_id: string;
|
||||||
|
sub_elem_name: string;
|
||||||
|
original_name: string;
|
||||||
|
sub_elem_description: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesLocationResult extends Record<string, SQLiteValue> {
|
||||||
|
loc_id: string;
|
||||||
|
series_id: string;
|
||||||
|
loc_name: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesLocationElementResult extends Record<string, SQLiteValue> {
|
||||||
|
element_id: string;
|
||||||
|
location_id: string;
|
||||||
|
element_name: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesLocationSubElementResult extends Record<string, SQLiteValue> {
|
||||||
|
sub_element_id: string;
|
||||||
|
element_id: string;
|
||||||
|
sub_elem_name: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SeriesLocationRepo {
|
||||||
|
/**
|
||||||
|
* Fetches all locations for a series.
|
||||||
|
*/
|
||||||
|
public static fetchLocations(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT loc_id, loc_name FROM series_locations WHERE user_id = ? AND series_id = ?';
|
||||||
|
const locations: SeriesLocationResult[] = db.all(query, [userId, seriesId]) as SeriesLocationResult[];
|
||||||
|
return locations;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les lieux.` : `Unable to retrieve locations.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all elements for a location.
|
||||||
|
*/
|
||||||
|
public static fetchElements(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationElementResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT element_id, location_id, element_name, element_description FROM series_location_elements WHERE user_id = ? AND location_id = ?';
|
||||||
|
const elements: SeriesLocationElementResult[] = db.all(query, [userId, locationId]) as SeriesLocationElementResult[];
|
||||||
|
return elements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments.` : `Unable to retrieve elements.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all sub-elements for an element.
|
||||||
|
*/
|
||||||
|
public static fetchSubElements(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationSubElementResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT sub_element_id, element_id, sub_elem_name, sub_elem_description FROM series_location_sub_elements WHERE user_id = ? AND element_id = ?';
|
||||||
|
const subElements: SeriesLocationSubElementResult[] = db.all(query, [userId, elementId]) as SeriesLocationSubElementResult[];
|
||||||
|
return subElements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les sous-éléments.` : `Unable to retrieve sub-elements.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new location section.
|
||||||
|
*/
|
||||||
|
public static insertLocation(locationId: string, seriesId: string, userId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_locations (loc_id, series_id, user_id, loc_name, loc_original_name, last_update) VALUES (?, ?, ?, ?, ?, ?)';
|
||||||
|
insertResult = db.run(query, [locationId, seriesId, userId, encryptedName, originalName, System.timeStampInSeconds()]);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible d'ajouter le lieu.` : `Unable to add location.`);
|
||||||
|
} else {
|
||||||
|
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' ? `Erreur lors de l'ajout du lieu.` : `Error adding location.`);
|
||||||
|
}
|
||||||
|
return locationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new element.
|
||||||
|
*/
|
||||||
|
public static insertElement(elementId: string, locationId: string, userId: string, encryptedName: string, originalName: string, description: string | null, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_location_elements (element_id, location_id, user_id, element_name, original_name, element_description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)';
|
||||||
|
insertResult = db.run(query, [elementId, locationId, userId, encryptedName, originalName, description, System.timeStampInSeconds()]);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible d'ajouter l'élément.` : `Unable to add element.`);
|
||||||
|
} else {
|
||||||
|
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' ? `Erreur lors de l'ajout de l'élément.` : `Error adding element.`);
|
||||||
|
}
|
||||||
|
return elementId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new sub-element.
|
||||||
|
*/
|
||||||
|
public static insertSubElement(subElementId: string, elementId: string, userId: string, encryptedName: string, originalName: string, description: string | null, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_location_sub_elements (sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)';
|
||||||
|
insertResult = db.run(query, [subElementId, elementId, userId, encryptedName, originalName, description, System.timeStampInSeconds()]);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible d'ajouter le sous-élément.` : `Unable to add sub-element.`);
|
||||||
|
} else {
|
||||||
|
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' ? `Erreur lors de l'ajout du sous-élément.` : `Error adding sub-element.`);
|
||||||
|
}
|
||||||
|
return subElementId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a location section.
|
||||||
|
*/
|
||||||
|
public static deleteLocation(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'DELETE FROM series_locations WHERE loc_id = ? AND user_id = ?';
|
||||||
|
const deleteResult: RunResult = db.run(query, [locationId, 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 lieu.` : `Unable to delete location.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an element.
|
||||||
|
*/
|
||||||
|
public static deleteElement(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'DELETE FROM series_location_elements WHERE element_id = ? AND user_id = ?';
|
||||||
|
const deleteResult: RunResult = db.run(query, [elementId, 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'élément.` : `Unable to delete element.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a sub-element.
|
||||||
|
*/
|
||||||
|
public static deleteSubElement(userId: string, subElementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'DELETE FROM series_location_sub_elements WHERE sub_element_id = ? AND user_id = ?';
|
||||||
|
const deleteResult: RunResult = db.run(query, [subElementId, 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 sous-élément.` : `Unable to delete sub-element.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a location's name.
|
||||||
|
*/
|
||||||
|
public static updateLocation(userId: string, locationId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE series_locations SET loc_name = ?, loc_original_name = ?, last_update = ? WHERE loc_id = ? AND user_id = ?';
|
||||||
|
const updateResult: RunResult = db.run(query, [encryptedName, originalName, System.timeStampInSeconds(), locationId, userId]);
|
||||||
|
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 lieu.` : `Unable to update location.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all locations for a series for sync.
|
||||||
|
*/
|
||||||
|
public static fetchSeriesLocationsTable(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT loc_id, series_id, user_id, loc_name, loc_original_name, last_update FROM series_locations WHERE series_id = ? AND user_id = ?';
|
||||||
|
const locations: SeriesLocationsTableResult[] = db.all(query, [seriesId, userId]) as SeriesLocationsTableResult[];
|
||||||
|
return locations;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les lieux pour sync.` : `Unable to retrieve locations for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all elements for a location for sync.
|
||||||
|
*/
|
||||||
|
public static fetchSeriesLocationElementsTable(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationElementsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT element_id, location_id, user_id, element_name, original_name, element_description, last_update FROM series_location_elements WHERE location_id = ? AND user_id = ?';
|
||||||
|
const elements: SeriesLocationElementsTableResult[] = db.all(query, [locationId, userId]) as SeriesLocationElementsTableResult[];
|
||||||
|
return elements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de lieu pour sync.` : `Unable to retrieve location elements for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all sub-elements for an element for sync.
|
||||||
|
*/
|
||||||
|
public static fetchSeriesLocationSubElementsTable(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationSubElementsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update FROM series_location_sub_elements WHERE element_id = ? AND user_id = ?';
|
||||||
|
const subElements: SeriesLocationSubElementsTableResult[] = db.all(query, [elementId, userId]) as SeriesLocationSubElementsTableResult[];
|
||||||
|
return subElements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les sous-éléments pour sync.` : `Unable to retrieve sub-elements for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all series locations for a user for sync comparison.
|
||||||
|
*/
|
||||||
|
public static fetchSyncedSeriesLocations(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesLocationResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT loc_id, series_id, loc_name, last_update FROM series_locations WHERE user_id = ?';
|
||||||
|
const locations: SyncedSeriesLocationResult[] = db.all(query, [userId]) as SyncedSeriesLocationResult[];
|
||||||
|
return locations;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les lieux de série pour sync.` : `Unable to retrieve series locations for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all series location elements for a user for sync comparison.
|
||||||
|
*/
|
||||||
|
public static fetchSyncedSeriesLocationElements(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesLocationElementResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT element_id, location_id, element_name, last_update FROM series_location_elements WHERE user_id = ?';
|
||||||
|
const elements: SyncedSeriesLocationElementResult[] = db.all(query, [userId]) as SyncedSeriesLocationElementResult[];
|
||||||
|
return elements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de lieu pour sync.` : `Unable to retrieve location elements for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all series location sub-elements for a user for sync comparison.
|
||||||
|
*/
|
||||||
|
public static fetchSyncedSeriesLocationSubElements(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesLocationSubElementResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT sub_element_id, element_id, sub_elem_name, last_update FROM series_location_sub_elements WHERE user_id = ?';
|
||||||
|
const subElements: SyncedSeriesLocationSubElementResult[] = db.all(query, [userId]) as SyncedSeriesLocationSubElementResult[];
|
||||||
|
return subElements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les sous-éléments de lieu pour sync.` : `Unable to retrieve location sub-elements for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a complete location by ID for sync.
|
||||||
|
*/
|
||||||
|
public static fetchCompleteLocationById(locationId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT loc_id, series_id, user_id, loc_name, loc_original_name, last_update FROM series_locations WHERE loc_id = ?';
|
||||||
|
const locations: SeriesLocationsTableResult[] = db.all(query, [locationId]) as SeriesLocationsTableResult[];
|
||||||
|
return locations;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer le lieu complet.` : `Unable to retrieve complete location.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a complete location element by ID for sync.
|
||||||
|
*/
|
||||||
|
public static fetchCompleteLocationElementById(elementId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationElementsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT element_id, location_id, user_id, element_name, original_name, element_description, last_update FROM series_location_elements WHERE element_id = ?';
|
||||||
|
const elements: SeriesLocationElementsTableResult[] = db.all(query, [elementId]) as SeriesLocationElementsTableResult[];
|
||||||
|
return elements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer l'élément de lieu complet.` : `Unable to retrieve complete location element.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a complete location sub-element by ID for sync.
|
||||||
|
*/
|
||||||
|
public static fetchCompleteLocationSubElementById(subElementId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationSubElementsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update FROM series_location_sub_elements WHERE sub_element_id = ?';
|
||||||
|
const subElements: SeriesLocationSubElementsTableResult[] = db.all(query, [subElementId]) as SeriesLocationSubElementsTableResult[];
|
||||||
|
return subElements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer le sous-élément complet.` : `Unable to retrieve complete sub-element.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a location exists.
|
||||||
|
*/
|
||||||
|
public static isLocationExist(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT 1 FROM series_locations WHERE loc_id = ? AND user_id = ?';
|
||||||
|
const result: QueryResult | null = db.get(query, [locationId, 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 lieu.` : `Unable to check location existence.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a location element exists.
|
||||||
|
*/
|
||||||
|
public static isLocationElementExist(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT 1 FROM series_location_elements WHERE element_id = ? AND user_id = ?';
|
||||||
|
const result: QueryResult | null = db.get(query, [elementId, 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'élément.` : `Unable to check element existence.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a location sub-element exists.
|
||||||
|
*/
|
||||||
|
public static isLocationSubElementExist(userId: string, subElementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT 1 FROM series_location_sub_elements WHERE sub_element_id = ? AND user_id = ?';
|
||||||
|
const result: QueryResult | null = db.get(query, [subElementId, 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 sous-élément.` : `Unable to check sub-element existence.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a series location for sync.
|
||||||
|
*/
|
||||||
|
public static insertSyncLocation(locationId: string, seriesId: string, userId: string, locName: string, locOriginalName: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_locations (loc_id, series_id, user_id, loc_name, loc_original_name, last_update) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(loc_id) DO UPDATE SET loc_name = excluded.loc_name, loc_original_name = excluded.loc_original_name, last_update = excluded.last_update';
|
||||||
|
const params: SQLiteValue[] = [locationId, seriesId, userId, locName, locOriginalName, 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 lieu pour sync.` : `Unable to insert location for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a series location for sync.
|
||||||
|
*/
|
||||||
|
public static updateSyncLocation(userId: string, locationId: string, locName: string, locOriginalName: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE series_locations SET loc_name = ?, loc_original_name = ?, last_update = ? WHERE loc_id = ? AND user_id = ?';
|
||||||
|
const params: SQLiteValue[] = [locName, locOriginalName, lastUpdate, locationId, 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 lieu pour sync.` : `Unable to update location for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a series location element for sync.
|
||||||
|
*/
|
||||||
|
public static insertSyncLocationElement(elementId: string, locationId: string, userId: string, elementName: string, originalName: string, elementDescription: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_location_elements (element_id, location_id, user_id, element_name, original_name, element_description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(element_id) DO UPDATE SET element_name = excluded.element_name, original_name = excluded.original_name, element_description = excluded.element_description, last_update = excluded.last_update';
|
||||||
|
const params: SQLiteValue[] = [elementId, locationId, userId, elementName, originalName, elementDescription, 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'élément de lieu pour sync.` : `Unable to insert location element for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a series location element for sync.
|
||||||
|
*/
|
||||||
|
public static updateSyncLocationElement(userId: string, elementId: string, elementName: string, originalName: string, elementDescription: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE series_location_elements SET element_name = ?, original_name = ?, element_description = ?, last_update = ? WHERE element_id = ? AND user_id = ?';
|
||||||
|
const params: SQLiteValue[] = [elementName, originalName, elementDescription, lastUpdate, elementId, 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'élément de lieu pour sync.` : `Unable to update location element for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a series location sub-element for sync.
|
||||||
|
*/
|
||||||
|
public static insertSyncLocationSubElement(subElementId: string, elementId: string, userId: string, subElemName: string, originalName: string, subElemDescription: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_location_sub_elements (sub_element_id, element_id, user_id, sub_elem_name, original_name, sub_elem_description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(sub_element_id) DO UPDATE SET sub_elem_name = excluded.sub_elem_name, original_name = excluded.original_name, sub_elem_description = excluded.sub_elem_description, last_update = excluded.last_update';
|
||||||
|
const params: SQLiteValue[] = [subElementId, elementId, userId, subElemName, originalName, subElemDescription, 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 sous-élément pour sync.` : `Unable to insert sub-element for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a series location sub-element for sync.
|
||||||
|
*/
|
||||||
|
public static updateSyncLocationSubElement(userId: string, subElementId: string, subElemName: string, originalName: string, subElemDescription: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE series_location_sub_elements SET sub_elem_name = ?, original_name = ?, sub_elem_description = ?, last_update = ? WHERE sub_element_id = ? AND user_id = ?';
|
||||||
|
const params: SQLiteValue[] = [subElemName, originalName, subElemDescription, lastUpdate, subElementId, 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 sous-élément pour sync.` : `Unable to update sub-element for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
499
electron/database/repositories/series-spell.repo.ts
Normal file
499
electron/database/repositories/series-spell.repo.ts
Normal file
@@ -0,0 +1,499 @@
|
|||||||
|
import { Database, QueryResult, RunResult, SQLiteValue } from 'node-sqlite3-wasm';
|
||||||
|
import System from "../System.js";
|
||||||
|
|
||||||
|
export interface SeriesSpellResult extends Record<string, SQLiteValue> {
|
||||||
|
spell_id: string;
|
||||||
|
series_id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
appearance: string;
|
||||||
|
tags: string;
|
||||||
|
power_level: string | null;
|
||||||
|
components: string | null;
|
||||||
|
limitations: string | null;
|
||||||
|
notes: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesSpellTagResult extends Record<string, SQLiteValue> {
|
||||||
|
tag_id: string;
|
||||||
|
name: string;
|
||||||
|
color: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesSpellsTableResult extends Record<string, SQLiteValue> {
|
||||||
|
spell_id: string;
|
||||||
|
series_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 SeriesSpellTagsTableResult extends Record<string, SQLiteValue> {
|
||||||
|
tag_id: string;
|
||||||
|
series_id: string;
|
||||||
|
user_id: string;
|
||||||
|
name: string;
|
||||||
|
hashed_name: string;
|
||||||
|
color: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesSpellResult extends Record<string, SQLiteValue> {
|
||||||
|
spell_id: string;
|
||||||
|
series_id: string;
|
||||||
|
name: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesSpellTagResult extends Record<string, SQLiteValue> {
|
||||||
|
tag_id: string;
|
||||||
|
series_id: string;
|
||||||
|
name: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SeriesSpellRepo {
|
||||||
|
/**
|
||||||
|
* Fetches all spells for a specific series.
|
||||||
|
*/
|
||||||
|
static fetchSpells(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT spell_id, series_id, name, description, appearance, tags, power_level, components, limitations, notes FROM series_spells WHERE user_id=? AND series_id=?';
|
||||||
|
const spells: SeriesSpellResult[] = db.all(query, [userId, seriesId]) as SeriesSpellResult[];
|
||||||
|
return spells;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les sorts.` : `Unable to retrieve spells.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a single spell by its ID.
|
||||||
|
*/
|
||||||
|
static fetchSpellById(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellResult | null {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT spell_id, series_id, name, description, appearance, tags, power_level, components, limitations, notes FROM series_spells WHERE user_id=? AND spell_id=?';
|
||||||
|
const spell: SeriesSpellResult | undefined = db.get(query, [userId, spellId]) as SeriesSpellResult | undefined;
|
||||||
|
return spell || null;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer le sort.` : `Unable to retrieve spell.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new spell.
|
||||||
|
*/
|
||||||
|
static insertSpell(spellId: string, seriesId: 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, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_spells (spell_id, series_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||||
|
const params: SQLiteValue[] = [spellId, seriesId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, 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 sort.` : `Unable to add spell.`);
|
||||||
|
} 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' ? `Erreur lors de l'ajout du sort.` : `Error adding spell.`);
|
||||||
|
}
|
||||||
|
return spellId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing spell.
|
||||||
|
*/
|
||||||
|
static updateSpell(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, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE series_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, System.timeStampInSeconds(), spellId, 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 sort.` : `Unable to update spell.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a spell.
|
||||||
|
*/
|
||||||
|
static deleteSpell(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'DELETE FROM series_spells WHERE spell_id=? AND user_id=?';
|
||||||
|
const deleteResult: RunResult = db.run(query, [spellId, 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 sort.` : `Unable to delete spell.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all spell tags for a series.
|
||||||
|
*/
|
||||||
|
static fetchTags(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellTagResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT tag_id, name, color FROM series_spell_tags WHERE user_id=? AND series_id=?';
|
||||||
|
const tags: SeriesSpellTagResult[] = db.all(query, [userId, seriesId]) as SeriesSpellTagResult[];
|
||||||
|
return tags;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les tags.` : `Unable to retrieve tags.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new spell tag.
|
||||||
|
*/
|
||||||
|
static insertTag(tagId: string, seriesId: string, userId: string, name: string, hashedName: string, color: string | null, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_spell_tags (tag_id, series_id, user_id, name, hashed_name, color, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)';
|
||||||
|
const params: SQLiteValue[] = [tagId, seriesId, userId, name, hashedName, color, 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 tag.` : `Unable to add tag.`);
|
||||||
|
} 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' ? `Erreur lors de l'ajout du tag.` : `Error adding tag.`);
|
||||||
|
}
|
||||||
|
return tagId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing spell tag.
|
||||||
|
*/
|
||||||
|
static updateTag(userId: string, tagId: string, name: string, hashedName: string, color: string | null, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE series_spell_tags SET name=?, hashed_name=?, color=?, last_update=? WHERE tag_id=? AND user_id=?';
|
||||||
|
const params: SQLiteValue[] = [name, hashedName, color, System.timeStampInSeconds(), tagId, 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 tag.` : `Unable to update tag.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a spell tag.
|
||||||
|
*/
|
||||||
|
static deleteTag(userId: string, tagId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'DELETE FROM series_spell_tags WHERE tag_id=? AND user_id=?';
|
||||||
|
const deleteResult: RunResult = db.run(query, [tagId, 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 tag.` : `Unable to delete tag.`);
|
||||||
|
} 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 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 series_spells WHERE spell_id=? AND user_id=?';
|
||||||
|
const result: QueryResult | null = db.get(query, [spellId, 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 sort.` : `Unable to check spell 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 spells for a series for sync.
|
||||||
|
*/
|
||||||
|
static fetchSeriesSpellsTable(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT spell_id, series_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM series_spells WHERE series_id = ? AND user_id = ?';
|
||||||
|
const spells: SeriesSpellsTableResult[] = db.all(query, [seriesId, userId]) as SeriesSpellsTableResult[];
|
||||||
|
return spells;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les sorts pour sync.` : `Unable to retrieve spells for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all spell tags for a series for sync.
|
||||||
|
*/
|
||||||
|
static fetchSeriesSpellTagsTable(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellTagsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT tag_id, series_id, user_id, name, hashed_name, color, last_update FROM series_spell_tags WHERE series_id = ? AND user_id = ?';
|
||||||
|
const tags: SeriesSpellTagsTableResult[] = db.all(query, [seriesId, userId]) as SeriesSpellTagsTableResult[];
|
||||||
|
return tags;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les tags de sort pour sync.` : `Unable to retrieve spell tags for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all series spells for a user for sync comparison.
|
||||||
|
*/
|
||||||
|
static fetchSyncedSeriesSpells(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesSpellResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT spell_id, series_id, name, last_update FROM series_spells WHERE user_id = ?';
|
||||||
|
const spells: SyncedSeriesSpellResult[] = db.all(query, [userId]) as SyncedSeriesSpellResult[];
|
||||||
|
return spells;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les sorts de série pour sync.` : `Unable to retrieve series spells for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all series spell tags for a user for sync comparison.
|
||||||
|
*/
|
||||||
|
static fetchSyncedSeriesSpellTags(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesSpellTagResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT tag_id, series_id, name, last_update FROM series_spell_tags WHERE user_id = ?';
|
||||||
|
const tags: SyncedSeriesSpellTagResult[] = db.all(query, [userId]) as SyncedSeriesSpellTagResult[];
|
||||||
|
return tags;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les tags de sort pour sync.` : `Unable to retrieve spell tags for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a complete spell by ID for sync.
|
||||||
|
*/
|
||||||
|
static fetchSpellTableById(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellsTableResult | null {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT spell_id, series_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM series_spells WHERE spell_id = ? AND user_id = ?';
|
||||||
|
const spell: SeriesSpellsTableResult | undefined = db.get(query, [spellId, userId]) as SeriesSpellsTableResult | undefined;
|
||||||
|
return spell || null;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer le sort complet.` : `Unable to retrieve complete spell.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a complete spell tag by ID for sync.
|
||||||
|
*/
|
||||||
|
static fetchSpellTagTableById(userId: string, tagId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellTagsTableResult | null {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT tag_id, series_id, user_id, name, hashed_name, color, last_update FROM series_spell_tags WHERE tag_id = ? AND user_id = ?';
|
||||||
|
const tag: SeriesSpellTagsTableResult | undefined = db.get(query, [tagId, userId]) as SeriesSpellTagsTableResult | undefined;
|
||||||
|
return tag || null;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer le tag complet.` : `Unable to retrieve complete tag.`);
|
||||||
|
} 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 spell tag exists.
|
||||||
|
*/
|
||||||
|
static isSpellTagExist(userId: string, tagId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT 1 FROM series_spell_tags WHERE tag_id=? AND user_id=?';
|
||||||
|
const result: QueryResult | null = db.get(query, [tagId, 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 tag.` : `Unable to check tag existence.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a series spell for sync.
|
||||||
|
*/
|
||||||
|
static insertSyncSpell(spellId: string, seriesId: 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 INTO series_spells (spell_id, series_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(spell_id) DO UPDATE SET name = excluded.name, name_hash = excluded.name_hash, description = excluded.description, appearance = excluded.appearance, tags = excluded.tags, power_level = excluded.power_level, components = excluded.components, limitations = excluded.limitations, notes = excluded.notes, last_update = excluded.last_update';
|
||||||
|
const params: SQLiteValue[] = [spellId, seriesId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, 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 sort pour sync.` : `Unable to insert spell for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a series spell for sync.
|
||||||
|
*/
|
||||||
|
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 series_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 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 sort pour sync.` : `Unable to update spell for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a series spell tag for sync.
|
||||||
|
*/
|
||||||
|
static insertSyncSpellTag(tagId: string, seriesId: string, userId: string, name: string, hashedName: string, color: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_spell_tags (tag_id, series_id, user_id, name, hashed_name, color, last_update) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(tag_id) DO UPDATE SET name = excluded.name, hashed_name = excluded.hashed_name, color = excluded.color, last_update = excluded.last_update';
|
||||||
|
const params: SQLiteValue[] = [tagId, seriesId, userId, name, hashedName, 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 tag pour sync.` : `Unable to insert tag for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a series spell tag for sync.
|
||||||
|
*/
|
||||||
|
static updateSyncSpellTag(userId: string, tagId: string, name: string, hashedName: string, color: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE series_spell_tags SET name = ?, hashed_name = ?, color = ?, last_update = ? WHERE tag_id = ? AND user_id = ?';
|
||||||
|
const params: SQLiteValue[] = [name, hashedName, color, lastUpdate, tagId, 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 tag pour sync.` : `Unable to update tag for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
258
electron/database/repositories/series-sync.repo.ts
Normal file
258
electron/database/repositories/series-sync.repo.ts
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
import { Database, QueryResult, RunResult, SQLiteValue } from 'node-sqlite3-wasm';
|
||||||
|
import System from "../System.js";
|
||||||
|
|
||||||
|
export type SyncElementType = 'character' | 'world' | 'location' | 'spell';
|
||||||
|
|
||||||
|
export interface BookElementSeriesLink extends Record<string, SQLiteValue> {
|
||||||
|
series_id: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SeriesSyncRepo {
|
||||||
|
/**
|
||||||
|
* Gets the series element ID linked to a book character.
|
||||||
|
*/
|
||||||
|
static getCharacterSeriesLink(userId: string, characterId: string, lang: 'fr' | 'en' = 'fr'): string | null {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series_character_id AS series_id FROM book_characters WHERE character_id = ? AND user_id = ?';
|
||||||
|
const result: BookElementSeriesLink | undefined = db.get(query, [characterId, userId]) as BookElementSeriesLink | undefined;
|
||||||
|
return result ? result.series_id : null;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer le lien série du personnage.` : `Unable to retrieve character series link.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the series element ID linked to a book world.
|
||||||
|
*/
|
||||||
|
static getWorldSeriesLink(userId: string, worldId: string, lang: 'fr' | 'en' = 'fr'): string | null {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series_world_id AS series_id FROM book_world WHERE world_id = ? AND user_id = ?';
|
||||||
|
const result: BookElementSeriesLink | undefined = db.get(query, [worldId, userId]) as BookElementSeriesLink | undefined;
|
||||||
|
return result ? result.series_id : null;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer le lien série du monde.` : `Unable to retrieve world series link.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the series element ID linked to a book location.
|
||||||
|
*/
|
||||||
|
static getLocationSeriesLink(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): string | null {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series_location_id AS series_id FROM book_location WHERE loc_id = ? AND user_id = ?';
|
||||||
|
const result: BookElementSeriesLink | undefined = db.get(query, [locationId, userId]) as BookElementSeriesLink | undefined;
|
||||||
|
return result ? result.series_id : null;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer le lien série du lieu.` : `Unable to retrieve location series link.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the series element ID linked to a book spell.
|
||||||
|
*/
|
||||||
|
static getSpellSeriesLink(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): string | null {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series_spell_id AS series_id FROM book_spells WHERE spell_id = ? AND user_id = ?';
|
||||||
|
const result: BookElementSeriesLink | undefined = db.get(query, [spellId, userId]) as BookElementSeriesLink | undefined;
|
||||||
|
return result ? result.series_id : null;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer le lien série du sort.` : `Unable to retrieve spell series link.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a field in series_characters table.
|
||||||
|
*/
|
||||||
|
static updateSeriesCharacterField(userId: string, seriesCharacterId: string, field: string, encryptedValue: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const allowedFields: string[] = ['first_name', 'last_name', 'nickname', 'age', 'gender', 'species', 'nationality', 'status', 'title', 'category', 'role', 'biography', 'history', 'speech_pattern', 'catchphrase', 'residence', 'notes', 'color'];
|
||||||
|
if (!allowedFields.includes(field)) {
|
||||||
|
throw new Error(lang === 'fr' ? `Champ non autorisé: ${field}` : `Field not allowed: ${field}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = `UPDATE series_characters SET ${field} = ?, last_update = ? WHERE character_id = ? AND user_id = ?`;
|
||||||
|
const result: RunResult = db.run(query, [encryptedValue, System.timeStampInSeconds(), seriesCharacterId, userId]);
|
||||||
|
return result.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 série.` : `Unable to update series character.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a field in all book_characters linked to a series character.
|
||||||
|
*/
|
||||||
|
static updateLinkedBookCharactersField(userId: string, seriesCharacterId: string, field: string, encryptedValue: string, lang: 'fr' | 'en' = 'fr'): number {
|
||||||
|
const allowedFields: string[] = ['first_name', 'last_name', 'nickname', 'age', 'gender', 'species', 'nationality', 'status', 'title', 'category', 'role', 'biography', 'history', 'speech_pattern', 'catchphrase', 'residence', 'notes', 'color'];
|
||||||
|
if (!allowedFields.includes(field)) {
|
||||||
|
throw new Error(lang === 'fr' ? `Champ non autorisé: ${field}` : `Field not allowed: ${field}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = `UPDATE book_characters SET ${field} = ?, last_update = ? WHERE series_character_id = ? AND user_id = ?`;
|
||||||
|
const result: RunResult = db.run(query, [encryptedValue, System.timeStampInSeconds(), seriesCharacterId, userId]);
|
||||||
|
return result.changes;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de mettre à jour les personnages liés.` : `Unable to update linked characters.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a field in series_worlds table.
|
||||||
|
*/
|
||||||
|
static updateSeriesWorldField(userId: string, seriesWorldId: string, field: string, encryptedValue: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const allowedFields: string[] = ['name', 'history', 'politics', 'economy', 'religion', 'languages'];
|
||||||
|
if (!allowedFields.includes(field)) {
|
||||||
|
throw new Error(lang === 'fr' ? `Champ non autorisé: ${field}` : `Field not allowed: ${field}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = `UPDATE series_worlds SET ${field} = ?, last_update = ? WHERE world_id = ? AND user_id = ?`;
|
||||||
|
const result: RunResult = db.run(query, [encryptedValue, System.timeStampInSeconds(), seriesWorldId, userId]);
|
||||||
|
return result.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 monde série.` : `Unable to update series world.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a field in all book_world linked to a series world.
|
||||||
|
*/
|
||||||
|
static updateLinkedBookWorldsField(userId: string, seriesWorldId: string, field: string, encryptedValue: string, lang: 'fr' | 'en' = 'fr'): number {
|
||||||
|
const allowedFields: string[] = ['name', 'history', 'politics', 'economy', 'religion', 'languages'];
|
||||||
|
if (!allowedFields.includes(field)) {
|
||||||
|
throw new Error(lang === 'fr' ? `Champ non autorisé: ${field}` : `Field not allowed: ${field}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = `UPDATE book_world SET ${field} = ?, last_update = ? WHERE series_world_id = ? AND user_id = ?`;
|
||||||
|
const result: RunResult = db.run(query, [encryptedValue, System.timeStampInSeconds(), seriesWorldId, userId]);
|
||||||
|
return result.changes;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de mettre à jour les mondes liés.` : `Unable to update linked worlds.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a field in series_locations table.
|
||||||
|
*/
|
||||||
|
static updateSeriesLocationField(userId: string, seriesLocationId: string, field: string, encryptedValue: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const allowedFields: string[] = ['name'];
|
||||||
|
if (!allowedFields.includes(field)) {
|
||||||
|
throw new Error(lang === 'fr' ? `Champ non autorisé: ${field}` : `Field not allowed: ${field}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = `UPDATE series_locations SET ${field} = ?, last_update = ? WHERE location_id = ? AND user_id = ?`;
|
||||||
|
const result: RunResult = db.run(query, [encryptedValue, System.timeStampInSeconds(), seriesLocationId, userId]);
|
||||||
|
return result.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 lieu série.` : `Unable to update series location.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a field in all book_location linked to a series location.
|
||||||
|
*/
|
||||||
|
static updateLinkedBookLocationsField(userId: string, seriesLocationId: string, field: string, encryptedValue: string, lang: 'fr' | 'en' = 'fr'): number {
|
||||||
|
const allowedFields: string[] = ['loc_name'];
|
||||||
|
if (!allowedFields.includes(field)) {
|
||||||
|
throw new Error(lang === 'fr' ? `Champ non autorisé: ${field}` : `Field not allowed: ${field}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = `UPDATE book_location SET ${field} = ?, last_update = ? WHERE series_location_id = ? AND user_id = ?`;
|
||||||
|
const result: RunResult = db.run(query, [encryptedValue, System.timeStampInSeconds(), seriesLocationId, userId]);
|
||||||
|
return result.changes;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de mettre à jour les lieux liés.` : `Unable to update linked locations.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a field in series_spells table.
|
||||||
|
*/
|
||||||
|
static updateSeriesSpellField(userId: string, seriesSpellId: string, field: string, encryptedValue: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
const allowedFields: string[] = ['name', 'description', 'type', 'level', 'range', 'duration', 'cost', 'effect', 'components', 'notes'];
|
||||||
|
if (!allowedFields.includes(field)) {
|
||||||
|
throw new Error(lang === 'fr' ? `Champ non autorisé: ${field}` : `Field not allowed: ${field}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = `UPDATE series_spells SET ${field} = ?, last_update = ? WHERE spell_id = ? AND user_id = ?`;
|
||||||
|
const result: RunResult = db.run(query, [encryptedValue, System.timeStampInSeconds(), seriesSpellId, userId]);
|
||||||
|
return result.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 sort série.` : `Unable to update series spell.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a field in all book_spells linked to a series spell.
|
||||||
|
*/
|
||||||
|
static updateLinkedBookSpellsField(userId: string, seriesSpellId: string, field: string, encryptedValue: string, lang: 'fr' | 'en' = 'fr'): number {
|
||||||
|
const allowedFields: string[] = ['name', 'description', 'type', 'level', 'range', 'duration', 'cost', 'effect', 'components', 'notes'];
|
||||||
|
if (!allowedFields.includes(field)) {
|
||||||
|
throw new Error(lang === 'fr' ? `Champ non autorisé: ${field}` : `Field not allowed: ${field}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = `UPDATE book_spells SET ${field} = ?, last_update = ? WHERE series_spell_id = ? AND user_id = ?`;
|
||||||
|
const result: RunResult = db.run(query, [encryptedValue, System.timeStampInSeconds(), seriesSpellId, userId]);
|
||||||
|
return result.changes;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de mettre à jour les sorts liés.` : `Unable to update linked spells.`);
|
||||||
|
}
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
432
electron/database/repositories/series-world.repo.ts
Normal file
432
electron/database/repositories/series-world.repo.ts
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
import { Database, QueryResult, RunResult, SQLiteValue } from 'node-sqlite3-wasm';
|
||||||
|
import System from "../System.js";
|
||||||
|
|
||||||
|
export interface SeriesWorldResult extends Record<string, SQLiteValue> {
|
||||||
|
world_id: string;
|
||||||
|
world_name: string;
|
||||||
|
history: string;
|
||||||
|
politics: string;
|
||||||
|
economy: string;
|
||||||
|
religion: string;
|
||||||
|
languages: string;
|
||||||
|
element_id: string;
|
||||||
|
element_name: string;
|
||||||
|
element_description: string;
|
||||||
|
element_type: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesWorldsTableResult extends Record<string, SQLiteValue> {
|
||||||
|
world_id: string;
|
||||||
|
series_id: string;
|
||||||
|
user_id: string;
|
||||||
|
name: string;
|
||||||
|
hashed_name: string;
|
||||||
|
history: string | null;
|
||||||
|
politics: string | null;
|
||||||
|
economy: string | null;
|
||||||
|
religion: string | null;
|
||||||
|
languages: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesWorldElementsTableResult extends Record<string, SQLiteValue> {
|
||||||
|
element_id: string;
|
||||||
|
world_id: string;
|
||||||
|
user_id: string;
|
||||||
|
element_type: number;
|
||||||
|
name: string;
|
||||||
|
original_name: string;
|
||||||
|
description: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesWorldResult extends Record<string, SQLiteValue> {
|
||||||
|
world_id: string;
|
||||||
|
series_id: string;
|
||||||
|
name: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesWorldElementResult extends Record<string, SQLiteValue> {
|
||||||
|
element_id: string;
|
||||||
|
world_id: string;
|
||||||
|
name: string;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SeriesWorldRepo {
|
||||||
|
/**
|
||||||
|
* Checks if a world with the given hashed name already exists for a user and series.
|
||||||
|
*/
|
||||||
|
public static checkWorldExist(userId: string, seriesId: string, worldName: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT world_id FROM series_worlds WHERE user_id=? AND series_id=? AND hashed_name=?';
|
||||||
|
const result: QueryResult | null = db.get(query, [userId, seriesId, worldName]);
|
||||||
|
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 monde.` : `Unable to verify world existence.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new world into the series.
|
||||||
|
*/
|
||||||
|
public static insertNewWorld(worldId: string, userId: string, seriesId: string, encryptedName: string, hashedName: string, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_worlds (world_id, user_id, series_id, name, hashed_name, last_update) VALUES (?,?,?,?,?,?)';
|
||||||
|
const params: SQLiteValue[] = [worldId, userId, seriesId, encryptedName, hashedName, 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 monde.` : `Unable to add world.`);
|
||||||
|
} 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' ? `Erreur lors de l'ajout du monde.` : `Error adding world.`);
|
||||||
|
}
|
||||||
|
return worldId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all worlds and their elements for a given series.
|
||||||
|
*/
|
||||||
|
public static fetchWorlds(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT world.world_id AS world_id, world.name AS world_name, world.history, world.politics, world.economy, world.religion, world.languages, element.element_id AS element_id, element.name AS element_name, element.description AS element_description, element.element_type FROM series_worlds AS world LEFT JOIN series_world_elements AS element ON world.world_id = element.world_id WHERE world.user_id = ? AND world.series_id = ?';
|
||||||
|
const worlds: SeriesWorldResult[] = db.all(query, [userId, seriesId]) as SeriesWorldResult[];
|
||||||
|
return worlds;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les mondes.` : `Unable to retrieve worlds.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a world's information.
|
||||||
|
*/
|
||||||
|
public static updateWorld(userId: string, worldId: string, encryptedName: string, hashedName: string, history: string | null, politics: string | null, economy: string | null, religion: string | null, languages: string | null, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE series_worlds SET name=?, hashed_name=?, history=?, politics=?, economy=?, religion=?, languages=?, last_update=? WHERE world_id=? AND user_id=?';
|
||||||
|
const params: SQLiteValue[] = [encryptedName, hashedName, history, politics, economy, religion, languages, System.timeStampInSeconds(), worldId, 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 monde.` : `Unable to update world.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new element for a world.
|
||||||
|
*/
|
||||||
|
public static insertElement(elementId: string, worldId: string, userId: string, elementType: number, encryptedName: string, originalName: string, description: string | null, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_world_elements (element_id, world_id, user_id, element_type, name, original_name, description, last_update) VALUES (?,?,?,?,?,?,?,?)';
|
||||||
|
const params: SQLiteValue[] = [elementId, worldId, userId, elementType, encryptedName, originalName, description, 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'élément.` : `Unable to add element.`);
|
||||||
|
} 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' ? `Erreur lors de l'ajout de l'élément.` : `Error adding element.`);
|
||||||
|
}
|
||||||
|
return elementId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes an element from a world.
|
||||||
|
*/
|
||||||
|
public static deleteElement(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'DELETE FROM series_world_elements WHERE element_id=? AND user_id=?';
|
||||||
|
const deleteResult: RunResult = db.run(query, [elementId, 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'élément.` : `Unable to delete element.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all worlds for a series for sync.
|
||||||
|
*/
|
||||||
|
public static fetchSeriesWorldsTable(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update FROM series_worlds WHERE series_id = ? AND user_id = ?';
|
||||||
|
const worlds: SeriesWorldsTableResult[] = db.all(query, [seriesId, userId]) as SeriesWorldsTableResult[];
|
||||||
|
return worlds;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les mondes pour sync.` : `Unable to retrieve worlds for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all elements for a world for sync.
|
||||||
|
*/
|
||||||
|
public static fetchSeriesWorldElementsTable(userId: string, worldId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldElementsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT element_id, world_id, user_id, element_type, name, original_name, description, last_update FROM series_world_elements WHERE world_id = ? AND user_id = ?';
|
||||||
|
const elements: SeriesWorldElementsTableResult[] = db.all(query, [worldId, userId]) as SeriesWorldElementsTableResult[];
|
||||||
|
return elements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de monde pour sync.` : `Unable to retrieve world elements for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all series worlds for a user for sync comparison.
|
||||||
|
*/
|
||||||
|
public static fetchSyncedSeriesWorlds(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesWorldResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT world_id, series_id, name, last_update FROM series_worlds WHERE user_id = ?';
|
||||||
|
const worlds: SyncedSeriesWorldResult[] = db.all(query, [userId]) as SyncedSeriesWorldResult[];
|
||||||
|
return worlds;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les mondes de série pour sync.` : `Unable to retrieve series worlds for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all series world elements for a user for sync comparison.
|
||||||
|
*/
|
||||||
|
public static fetchSyncedSeriesWorldElements(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesWorldElementResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT element_id, world_id, name, last_update FROM series_world_elements WHERE user_id = ?';
|
||||||
|
const elements: SyncedSeriesWorldElementResult[] = db.all(query, [userId]) as SyncedSeriesWorldElementResult[];
|
||||||
|
return elements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de monde pour sync.` : `Unable to retrieve world elements for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a complete world by ID for sync.
|
||||||
|
*/
|
||||||
|
public static fetchCompleteWorldById(worldId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update FROM series_worlds WHERE world_id = ?';
|
||||||
|
const worlds: SeriesWorldsTableResult[] = db.all(query, [worldId]) as SeriesWorldsTableResult[];
|
||||||
|
return worlds;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer le monde complet.` : `Unable to retrieve complete world.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a complete world element by ID for sync.
|
||||||
|
*/
|
||||||
|
public static fetchCompleteWorldElementById(elementId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldElementsTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT element_id, world_id, user_id, element_type, name, original_name, description, last_update FROM series_world_elements WHERE element_id = ?';
|
||||||
|
const elements: SeriesWorldElementsTableResult[] = db.all(query, [elementId]) as SeriesWorldElementsTableResult[];
|
||||||
|
return elements;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer l'élément de monde complet.` : `Unable to retrieve complete world element.`);
|
||||||
|
} 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 world exists.
|
||||||
|
*/
|
||||||
|
public static isWorldExist(userId: string, worldId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT 1 FROM series_worlds WHERE world_id=? AND user_id=?';
|
||||||
|
const result: QueryResult | null = db.get(query, [worldId, 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 monde.` : `Unable to check world existence.`);
|
||||||
|
} 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 world element exists.
|
||||||
|
*/
|
||||||
|
public static isWorldElementExist(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT 1 FROM series_world_elements WHERE element_id=? AND user_id=?';
|
||||||
|
const result: QueryResult | null = db.get(query, [elementId, 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'élément.` : `Unable to check element existence.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a series world for sync.
|
||||||
|
*/
|
||||||
|
public static insertSyncWorld(worldId: string, seriesId: string, userId: string, name: string, hashedName: string, history: string | null, politics: string | null, economy: string | null, religion: string | null, languages: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_worlds (world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(world_id) DO UPDATE SET name = excluded.name, hashed_name = excluded.hashed_name, history = excluded.history, politics = excluded.politics, economy = excluded.economy, religion = excluded.religion, languages = excluded.languages, last_update = excluded.last_update';
|
||||||
|
const params: SQLiteValue[] = [worldId, seriesId, userId, name, hashedName, history, politics, economy, religion, languages, 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 monde pour sync.` : `Unable to insert world for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a series world for sync.
|
||||||
|
*/
|
||||||
|
public static updateSyncWorld(userId: string, worldId: string, name: string, hashedName: string, history: string | null, politics: string | null, economy: string | null, religion: string | null, languages: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE series_worlds SET name = ?, hashed_name = ?, history = ?, politics = ?, economy = ?, religion = ?, languages = ?, last_update = ? WHERE world_id = ? AND user_id = ?';
|
||||||
|
const params: SQLiteValue[] = [name, hashedName, history, politics, economy, religion, languages, lastUpdate, worldId, 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 monde pour sync.` : `Unable to update world for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a series world element for sync.
|
||||||
|
*/
|
||||||
|
public static insertSyncWorldElement(elementId: string, worldId: string, userId: string, elementType: number, name: string, originalName: string, description: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_world_elements (element_id, world_id, user_id, element_type, name, original_name, description, last_update) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(element_id) DO UPDATE SET element_type = excluded.element_type, name = excluded.name, original_name = excluded.original_name, description = excluded.description, last_update = excluded.last_update';
|
||||||
|
const params: SQLiteValue[] = [elementId, worldId, userId, elementType, name, originalName, description, 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'élément de monde pour sync.` : `Unable to insert world element for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a series world element for sync.
|
||||||
|
*/
|
||||||
|
public static updateSyncWorldElement(userId: string, elementId: string, elementType: number, name: string, originalName: string, description: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE series_world_elements SET element_type = ?, name = ?, original_name = ?, description = ?, last_update = ? WHERE element_id = ? AND user_id = ?';
|
||||||
|
const params: SQLiteValue[] = [elementType, name, originalName, description, lastUpdate, elementId, 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'élément de monde pour sync.` : `Unable to update world element for sync.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
542
electron/database/repositories/series.repo.ts
Normal file
542
electron/database/repositories/series.repo.ts
Normal file
@@ -0,0 +1,542 @@
|
|||||||
|
import { Database, QueryResult, RunResult, SQLiteValue } from 'node-sqlite3-wasm';
|
||||||
|
import System from "../System.js";
|
||||||
|
|
||||||
|
export interface SeriesResult extends Record<string, SQLiteValue> {
|
||||||
|
series_id: string;
|
||||||
|
user_id: string;
|
||||||
|
name: string;
|
||||||
|
hashed_name: string;
|
||||||
|
description: string | null;
|
||||||
|
cover_image: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesBookResult extends Record<string, SQLiteValue> {
|
||||||
|
series_id: string;
|
||||||
|
book_id: string;
|
||||||
|
book_order: number;
|
||||||
|
title: string;
|
||||||
|
cover_image: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesListItem extends Record<string, SQLiteValue> {
|
||||||
|
series_id: string;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
cover_image: string | null;
|
||||||
|
book_count: number;
|
||||||
|
book_ids: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesTableResult extends Record<string, SQLiteValue> {
|
||||||
|
series_id: string;
|
||||||
|
user_id: string;
|
||||||
|
name: string;
|
||||||
|
hashed_name: string;
|
||||||
|
description: string | null;
|
||||||
|
cover_image: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeriesBooksTableResult extends Record<string, SQLiteValue> {
|
||||||
|
series_id: string;
|
||||||
|
book_id: string;
|
||||||
|
book_order: number;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesResult extends Record<string, SQLiteValue> {
|
||||||
|
series_id: string;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SyncedSeriesBookResult extends Record<string, SQLiteValue> {
|
||||||
|
series_id: string;
|
||||||
|
book_id: string;
|
||||||
|
book_order: number;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SeriesRepo {
|
||||||
|
/**
|
||||||
|
* Fetches all series 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 series with book counts
|
||||||
|
*/
|
||||||
|
public static fetchUserSeries(userId: string, lang: 'fr' | 'en' = 'fr'): SeriesListItem[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series.series_id, series.name, series.description, series.cover_image, COUNT(series_books.book_id) AS book_count, GROUP_CONCAT(series_books.book_id) AS book_ids FROM book_series series LEFT JOIN series_books ON series.series_id = series_books.series_id WHERE series.user_id = ? GROUP BY series.series_id, series.last_update ORDER BY series.last_update DESC';
|
||||||
|
const series: SeriesListItem[] = db.all(query, [userId]) as SeriesListItem[];
|
||||||
|
return series;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les séries.` : `Unable to retrieve series.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a single series by its ID.
|
||||||
|
* @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 The series result or null if not found
|
||||||
|
*/
|
||||||
|
public static fetchSeriesById(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesResult | null {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series_id, user_id, name, hashed_name, description, cover_image, last_update FROM book_series WHERE series_id = ? AND user_id = ?';
|
||||||
|
const series: SeriesResult | undefined = db.get(query, [seriesId, userId]) as SeriesResult | undefined;
|
||||||
|
return series || null;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer la série.` : `Unable to retrieve series.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new series.
|
||||||
|
* @param seriesId - The unique identifier for the new series
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param name - The encrypted name
|
||||||
|
* @param hashedName - The hashed name for duplicate detection
|
||||||
|
* @param description - The encrypted description (nullable)
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The series ID if successful
|
||||||
|
*/
|
||||||
|
public static insertSeries(seriesId: string, userId: string, name: string, hashedName: string, description: string | null, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let insertResult: RunResult;
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO book_series (series_id, user_id, name, hashed_name, description, last_update) VALUES (?, ?, ?, ?, ?, ?)';
|
||||||
|
const params: SQLiteValue[] = [seriesId, userId, name, hashedName, description, 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 de créer la série.` : `Unable to create series.`);
|
||||||
|
} 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 la création de la série.` : `Error creating series.`);
|
||||||
|
}
|
||||||
|
return seriesId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates an existing series.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param name - The encrypted name
|
||||||
|
* @param hashedName - The hashed name
|
||||||
|
* @param description - The encrypted description (nullable)
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the update was successful
|
||||||
|
*/
|
||||||
|
public static updateSeries(userId: string, seriesId: string, name: string, hashedName: string, description: string | null, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE book_series SET name = ?, hashed_name = ?, description = ?, last_update = ? WHERE series_id = ? AND user_id = ?';
|
||||||
|
const params: SQLiteValue[] = [name, hashedName, description, System.timeStampInSeconds(), seriesId, 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 la série.` : `Unable to update series.`);
|
||||||
|
} 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.
|
||||||
|
* @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 True if the deletion was successful
|
||||||
|
*/
|
||||||
|
public static deleteSeries(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'DELETE FROM book_series WHERE series_id = ? AND user_id = ?';
|
||||||
|
const params: SQLiteValue[] = [seriesId, userId];
|
||||||
|
const deleteResult: RunResult = db.run(query, params);
|
||||||
|
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 la série.` : `Unable to delete series.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all books in a series with their order.
|
||||||
|
* @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 books in the series
|
||||||
|
*/
|
||||||
|
public static fetchSeriesBooks(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesBookResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT sb.series_id, sb.book_id, sb.book_order, b.title, b.cover_image FROM series_books sb INNER JOIN erit_books b ON sb.book_id = b.book_id WHERE sb.series_id = ? AND b.author_id = ? ORDER BY sb.book_order';
|
||||||
|
const books: SeriesBookResult[] = db.all(query, [seriesId, userId]) as SeriesBookResult[];
|
||||||
|
return books;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les livres de la série.` : `Unable to retrieve series books.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a book to a series.
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param bookId - The unique identifier of the book
|
||||||
|
* @param bookOrder - The order of the book in the series
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the addition was successful
|
||||||
|
*/
|
||||||
|
public static addBookToSeries(seriesId: string, bookId: string, bookOrder: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_books (series_id, book_id, book_order, last_update) VALUES (?, ?, ?, ?) ON CONFLICT(series_id, book_id) DO UPDATE SET book_order = excluded.book_order, last_update = excluded.last_update';
|
||||||
|
const params: SQLiteValue[] = [seriesId, bookId, bookOrder, System.timeStampInSeconds()];
|
||||||
|
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'ajouter le livre à la série.` : `Unable to add book to series.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a book from a series.
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param bookId - The unique identifier of the book
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the removal was successful
|
||||||
|
*/
|
||||||
|
public static removeBookFromSeries(seriesId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'DELETE FROM series_books WHERE series_id = ? AND book_id = ?';
|
||||||
|
const params: SQLiteValue[] = [seriesId, bookId];
|
||||||
|
const deleteResult: RunResult = db.run(query, params);
|
||||||
|
return deleteResult.changes > 0;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de retirer le livre de la série.` : `Unable to remove book from series.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the order of books in a series.
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param booksOrder - An array of {bookId, order} objects
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the update was successful
|
||||||
|
*/
|
||||||
|
public static updateBooksOrder(seriesId: string, booksOrder: {bookId: string, order: number}[], lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const timestamp: number = System.timeStampInSeconds();
|
||||||
|
for (const bookOrder of booksOrder) {
|
||||||
|
const query: string = 'UPDATE series_books SET book_order = ?, last_update = ? WHERE series_id = ? AND book_id = ?';
|
||||||
|
db.run(query, [bookOrder.order, timestamp, seriesId, bookOrder.bookId]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de réordonner les livres.` : `Unable to reorder books.`);
|
||||||
|
} 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 exists for a 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 True if the series exists
|
||||||
|
*/
|
||||||
|
public static isSeriesExist(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT 1 FROM book_series WHERE series_id = ? AND user_id = ?';
|
||||||
|
const params: SQLiteValue[] = [seriesId, userId];
|
||||||
|
const result: QueryResult | null = db.get(query, params);
|
||||||
|
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 la série.` : `Unable to check series existence.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the series ID for a book if it belongs to one.
|
||||||
|
* @param bookId - The unique identifier of the book
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns The series ID or null
|
||||||
|
*/
|
||||||
|
public static getSeriesIdForBook(bookId: string, lang: 'fr' | 'en' = 'fr'): string | null {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series_id FROM series_books WHERE book_id = ?';
|
||||||
|
const result = db.get(query, [bookId]) as { series_id: string } | undefined;
|
||||||
|
return result ? result.series_id : null;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de vérifier la série du livre.` : `Unable to check book series.`);
|
||||||
|
} else {
|
||||||
|
console.error("An unknown error occurred.");
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a series table row for sync purposes.
|
||||||
|
* @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 containing the series table row
|
||||||
|
*/
|
||||||
|
public static fetchSeriesTableForSync(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series_id, user_id, name, hashed_name, description, cover_image, last_update FROM book_series WHERE series_id = ? AND user_id = ?';
|
||||||
|
const series: SeriesTableResult[] = db.all(query, [seriesId, userId]) as SeriesTableResult[];
|
||||||
|
return series;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer la série pour sync.` : `Unable to retrieve series for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all series-books relationships for sync.
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns An array of series-books table rows
|
||||||
|
*/
|
||||||
|
public static fetchSeriesBooksTable(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesBooksTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series_id, book_id, book_order, last_update FROM series_books WHERE series_id = ? ORDER BY book_order';
|
||||||
|
const books: SeriesBooksTableResult[] = db.all(query, [seriesId]) as SeriesBooksTableResult[];
|
||||||
|
return books;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les livres de la série pour sync.` : `Unable to retrieve series books for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all series for a user for sync comparison.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns An array of synced series results
|
||||||
|
*/
|
||||||
|
public static fetchSyncedSeries(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series_id, name, description, last_update FROM book_series WHERE user_id = ? ORDER BY last_update DESC';
|
||||||
|
const series: SyncedSeriesResult[] = db.all(query, [userId]) as SyncedSeriesResult[];
|
||||||
|
return series;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les séries pour sync.` : `Unable to retrieve series for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all series-books relationships for a user for sync comparison.
|
||||||
|
* @param userId - The unique identifier of the user
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns An array of synced series book results
|
||||||
|
*/
|
||||||
|
public static fetchSyncedSeriesBooks(userId: string, lang: 'fr' | 'en' = 'fr'): SyncedSeriesBookResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT sb.series_id, sb.book_id, sb.book_order, sb.last_update FROM series_books sb INNER JOIN book_series bs ON sb.series_id = bs.series_id WHERE bs.user_id = ? ORDER BY sb.book_order';
|
||||||
|
const books: SyncedSeriesBookResult[] = db.all(query, [userId]) as SyncedSeriesBookResult[];
|
||||||
|
return books;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer les livres de séries pour sync.` : `Unable to retrieve series books for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a complete series by ID for sync.
|
||||||
|
* @param seriesId - The unique identifier of the series
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns An array containing the series
|
||||||
|
*/
|
||||||
|
public static fetchCompleteSeriesById(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesTableResult[] {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT series_id, user_id, name, hashed_name, description, cover_image, last_update FROM book_series WHERE series_id = ?';
|
||||||
|
const series: SeriesTableResult[] = db.all(query, [seriesId]) as SeriesTableResult[];
|
||||||
|
return series;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
console.error(`DB Error: ${error.message}`);
|
||||||
|
throw new Error(lang === 'fr' ? `Impossible de récupérer la série complète.` : `Unable to retrieve complete series.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a series for sync purposes.
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the insertion was successful
|
||||||
|
*/
|
||||||
|
public static insertSyncSeries(seriesId: string, userId: string, name: string, hashedName: string, description: string | null, coverImage: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO book_series (series_id, user_id, name, hashed_name, description, cover_image, last_update) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(series_id) DO UPDATE SET name = excluded.name, hashed_name = excluded.hashed_name, description = excluded.description, cover_image = excluded.cover_image, last_update = excluded.last_update';
|
||||||
|
const params: SQLiteValue[] = [seriesId, userId, name, hashedName, description, coverImage, 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 la série pour sync.` : `Unable to insert series for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a series for sync purposes.
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the update was successful
|
||||||
|
*/
|
||||||
|
public static updateSyncSeries(userId: string, seriesId: string, name: string, hashedName: string, description: string | null, coverImage: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'UPDATE book_series SET name = ?, hashed_name = ?, description = ?, cover_image = ?, last_update = ? WHERE series_id = ? AND user_id = ?';
|
||||||
|
const params: SQLiteValue[] = [name, hashedName, description, coverImage, lastUpdate, seriesId, 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 la série pour sync.` : `Unable to update series for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a series-book relationship for sync.
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the insertion was successful
|
||||||
|
*/
|
||||||
|
public static insertSyncSeriesBook(seriesId: string, bookId: string, bookOrder: number, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'INSERT INTO series_books (series_id, book_id, book_order, last_update) VALUES (?, ?, ?, ?) ON CONFLICT(series_id, book_id) DO UPDATE SET book_order = excluded.book_order, last_update = excluded.last_update';
|
||||||
|
const params: SQLiteValue[] = [seriesId, bookId, bookOrder, 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 la liaison série-livre pour sync.` : `Unable to insert series-book for sync.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a series-book relationship exists.
|
||||||
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
|
* @returns True if the relationship exists
|
||||||
|
*/
|
||||||
|
public static isSeriesBookExist(seriesId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||||
|
try {
|
||||||
|
const db: Database = System.getDb();
|
||||||
|
const query: string = 'SELECT 1 FROM series_books WHERE series_id = ? AND book_id = ?';
|
||||||
|
const result: QueryResult | null = db.get(query, [seriesId, bookId]);
|
||||||
|
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 la liaison série-livre.` : `Unable to check series-book.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ export interface SpellResult extends Record<string, SQLiteValue> {
|
|||||||
components: string | null;
|
components: string | null;
|
||||||
limitations: string | null;
|
limitations: string | null;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
|
series_spell_id: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BookSpellsTable extends Record<string, SQLiteValue> {
|
export interface BookSpellsTable extends Record<string, SQLiteValue> {
|
||||||
@@ -48,7 +49,7 @@ export default class SpellRepo {
|
|||||||
static fetchSpells(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): SpellResult[] {
|
static fetchSpells(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): SpellResult[] {
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = 'SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes FROM book_spells WHERE user_id=? AND book_id=?';
|
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];
|
const params: SQLiteValue[] = [userId, bookId];
|
||||||
return db.all(query, params) as SpellResult[];
|
return db.all(query, params) as SpellResult[];
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
@@ -71,7 +72,7 @@ export default class SpellRepo {
|
|||||||
static fetchSpellById(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): SpellResult | null {
|
static fetchSpellById(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): SpellResult | null {
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = 'SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes FROM book_spells WHERE user_id=? AND spell_id=?';
|
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 params: SQLiteValue[] = [userId, spellId];
|
||||||
const spells: SpellResult[] = db.all(query, params) as SpellResult[];
|
const spells: SpellResult[] = db.all(query, params) as SpellResult[];
|
||||||
return spells.length > 0 ? spells[0] : null;
|
return spells.length > 0 ? spells[0] : null;
|
||||||
@@ -102,16 +103,17 @@ export default class SpellRepo {
|
|||||||
* @param lang - The language for error messages ('fr' or 'en')
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
* @returns The spell ID if successful
|
* @returns The spell ID if successful
|
||||||
*/
|
*/
|
||||||
static insertSpell(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, lang: 'fr' | 'en' = 'fr'): string {
|
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 {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = '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 query: string = seriesSpellId
|
||||||
const params: SQLiteValue[] = [spellId, bookId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, System.timeStampInSeconds()];
|
? '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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?)'
|
||||||
const result: RunResult = db.run(query, params);
|
: 'INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)';
|
||||||
if (!result || result.changes === 0) {
|
const params: SQLiteValue[] = seriesSpellId
|
||||||
throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du sort.` : `Error adding spell.`);
|
? [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()];
|
||||||
return spellId;
|
result = db.run(query, params);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(`[SpellRepo] DB Error: ${error.message}`);
|
console.error(`[SpellRepo] DB Error: ${error.message}`);
|
||||||
@@ -120,6 +122,10 @@ export default class SpellRepo {
|
|||||||
}
|
}
|
||||||
throw new Error(lang === 'fr' ? `Impossible d'ajouter le sort.` : `Unable to add spell.`);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,11 +144,15 @@ export default class SpellRepo {
|
|||||||
* @param lang - The language for error messages ('fr' or 'en')
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
* @returns True if the update was successful
|
* @returns True if the update was successful
|
||||||
*/
|
*/
|
||||||
static updateSpell(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, lang: 'fr' | 'en' = 'fr'): boolean {
|
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 {
|
try {
|
||||||
const db: Database = System.getDb();
|
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 query: string = seriesSpellId !== null
|
||||||
const params: SQLiteValue[] = [name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, System.timeStampInSeconds(), spellId, userId];
|
? '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);
|
const result: RunResult = db.run(query, params);
|
||||||
return result.changes > 0;
|
return result.changes > 0;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
|||||||
@@ -62,15 +62,12 @@ export default class SpellTagRepo {
|
|||||||
* @returns The tag ID if successful
|
* @returns The tag ID if successful
|
||||||
*/
|
*/
|
||||||
static insertSpellTag(tagId: string, bookId: string, userId: string, name: string, nameHash: string, color: string | null, lang: 'fr' | 'en' = 'fr'): string {
|
static insertSpellTag(tagId: string, bookId: string, userId: string, name: string, nameHash: string, color: string | null, lang: 'fr' | 'en' = 'fr'): string {
|
||||||
|
let result: RunResult;
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = 'INSERT INTO book_spell_tags (tag_id, book_id, user_id, name, name_hash, color, last_update) VALUES (?,?,?,?,?,?,?)';
|
const query: string = 'INSERT INTO book_spell_tags (tag_id, book_id, user_id, name, name_hash, color, last_update) VALUES (?,?,?,?,?,?,?)';
|
||||||
const params: SQLiteValue[] = [tagId, bookId, userId, name, nameHash, color, System.timeStampInSeconds()];
|
const params: SQLiteValue[] = [tagId, bookId, userId, name, nameHash, color, System.timeStampInSeconds()];
|
||||||
const result: RunResult = db.run(query, params);
|
result = db.run(query, params);
|
||||||
if (!result || result.changes === 0) {
|
|
||||||
throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du tag.` : `Error adding tag.`);
|
|
||||||
}
|
|
||||||
return tagId;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.error(`[SpellTagRepo] DB Error: ${error.message}`);
|
console.error(`[SpellTagRepo] DB Error: ${error.message}`);
|
||||||
@@ -79,6 +76,10 @@ export default class SpellTagRepo {
|
|||||||
}
|
}
|
||||||
throw new Error(lang === 'fr' ? `Impossible d'ajouter le tag de sort.` : `Unable to add spell tag.`);
|
throw new Error(lang === 'fr' ? `Impossible d'ajouter le tag de sort.` : `Unable to add spell tag.`);
|
||||||
}
|
}
|
||||||
|
if (!result || result.changes === 0) {
|
||||||
|
throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du tag.` : `Error adding tag.`);
|
||||||
|
}
|
||||||
|
return tagId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export interface WorldQuery extends Record<string, SQLiteValue> {
|
|||||||
element_name: string | null;
|
element_name: string | null;
|
||||||
element_description: string | null;
|
element_description: string | null;
|
||||||
element_type: number | null;
|
element_type: number | null;
|
||||||
|
series_world_id: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorldElementValue {
|
export interface WorldElementValue {
|
||||||
@@ -98,12 +99,16 @@ export default class WorldRepository {
|
|||||||
* @param lang - The language for error messages ('fr' or 'en')
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
* @returns The world ID if insertion was successful
|
* @returns The world ID if insertion was successful
|
||||||
*/
|
*/
|
||||||
public static insertNewWorld(worldId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, lang: 'fr' | 'en'): string {
|
public static insertNewWorld(worldId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, lang: 'fr' | 'en', seriesWorldId: string | null = null): string {
|
||||||
let insertResult: RunResult;
|
let insertResult: RunResult;
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = 'INSERT INTO book_world (world_id, author_id, book_id, name, hashed_name, last_update) VALUES (?, ?, ?, ?, ?, ?)';
|
const query: string = seriesWorldId
|
||||||
const params: SQLiteValue[] = [worldId, userId, bookId, encryptedName, hashedName, System.timeStampInSeconds()];
|
? 'INSERT INTO book_world (world_id, author_id, book_id, name, hashed_name, series_world_id, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||||||
|
: 'INSERT INTO book_world (world_id, author_id, book_id, name, hashed_name, last_update) VALUES (?, ?, ?, ?, ?, ?)';
|
||||||
|
const params: SQLiteValue[] = seriesWorldId
|
||||||
|
? [worldId, userId, bookId, encryptedName, hashedName, seriesWorldId, System.timeStampInSeconds()]
|
||||||
|
: [worldId, userId, bookId, encryptedName, hashedName, System.timeStampInSeconds()];
|
||||||
insertResult = db.run(query, params);
|
insertResult = db.run(query, params);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
@@ -130,7 +135,7 @@ export default class WorldRepository {
|
|||||||
public static fetchWorlds(userId: string, bookId: string, lang: 'fr' | 'en'): WorldQuery[] {
|
public static fetchWorlds(userId: string, bookId: string, lang: 'fr' | 'en'): WorldQuery[] {
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = `SELECT world.world_id AS world_id, world.name AS world_name, world.history, world.politics, world.economy, world.religion, world.languages, element.element_id AS element_id, element.name AS element_name, element.description AS element_description, element.element_type FROM book_world AS world LEFT JOIN book_world_elements AS element ON world.world_id=element.world_id WHERE world.author_id=? AND world.book_id=?`;
|
const query: string = 'SELECT world.world_id AS world_id, world.name AS world_name, world.history, world.politics, world.economy, world.religion, world.languages, element.element_id AS element_id, element.name AS element_name, element.description AS element_description, element.element_type, world.series_world_id FROM book_world AS world LEFT JOIN book_world_elements AS element ON world.world_id=element.world_id WHERE world.author_id=? AND world.book_id=?';
|
||||||
const params: SQLiteValue[] = [userId, bookId];
|
const params: SQLiteValue[] = [userId, bookId];
|
||||||
const worlds: WorldQuery[] = db.all(query, params) as WorldQuery[];
|
const worlds: WorldQuery[] = db.all(query, params) as WorldQuery[];
|
||||||
return worlds;
|
return worlds;
|
||||||
@@ -160,11 +165,15 @@ export default class WorldRepository {
|
|||||||
* @param lang - The language for error messages ('fr' or 'en')
|
* @param lang - The language for error messages ('fr' or 'en')
|
||||||
* @returns True if the update was successful, false otherwise
|
* @returns True if the update was successful, false otherwise
|
||||||
*/
|
*/
|
||||||
public static updateWorld(userId: string, worldId: string, encryptName: string, hashedName: string, encryptHistory: string, encryptPolitics: string, encryptEconomy: string, encryptReligion: string, encryptLanguages: string, lastUpdate: number, lang: 'fr' | 'en'): boolean {
|
public static updateWorld(userId: string, worldId: string, encryptName: string, hashedName: string, encryptHistory: string, encryptPolitics: string, encryptEconomy: string, encryptReligion: string, encryptLanguages: string, lastUpdate: number, lang: 'fr' | 'en', seriesWorldId: string | null = null): boolean {
|
||||||
try {
|
try {
|
||||||
const db: Database = System.getDb();
|
const db: Database = System.getDb();
|
||||||
const query: string = 'UPDATE book_world SET name=?, hashed_name=?, history=?, politics=?, economy=?, religion=?, languages=?, last_update=? WHERE author_id=? AND world_id=?';
|
const query: string = seriesWorldId !== null
|
||||||
const params: SQLiteValue[] = [encryptName, hashedName, encryptHistory, encryptPolitics, encryptEconomy, encryptReligion, encryptLanguages, lastUpdate, userId, worldId];
|
? 'UPDATE book_world SET name=?, hashed_name=?, history=?, politics=?, economy=?, religion=?, languages=?, last_update=?, series_world_id=? WHERE author_id=? AND world_id=?'
|
||||||
|
: 'UPDATE book_world SET name=?, hashed_name=?, history=?, politics=?, economy=?, religion=?, languages=?, last_update=? WHERE author_id=? AND world_id=?';
|
||||||
|
const params: SQLiteValue[] = seriesWorldId !== null
|
||||||
|
? [encryptName, hashedName, encryptHistory, encryptPolitics, encryptEconomy, encryptReligion, encryptLanguages, lastUpdate, seriesWorldId, userId, worldId]
|
||||||
|
: [encryptName, hashedName, encryptHistory, encryptPolitics, encryptEconomy, encryptReligion, encryptLanguages, lastUpdate, userId, worldId];
|
||||||
const updateResult: RunResult = db.run(query, params);
|
const updateResult: RunResult = db.run(query, params);
|
||||||
return updateResult.changes > 0;
|
return updateResult.changes > 0;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type Database = sqlite3.Database;
|
|||||||
// MIGRATIONS
|
// MIGRATIONS
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
const schemaVersion = 2;
|
const schemaVersion = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DEV ONLY - S'exécute à chaque refresh, pas besoin de version
|
* DEV ONLY - S'exécute à chaque refresh, pas besoin de version
|
||||||
@@ -137,6 +137,55 @@ function migrateFromOldSystem(db: Database): void {
|
|||||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_book ON book_spells(book_id)`);
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_book ON book_spells(book_id)`);
|
||||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_user ON book_spells(user_id)`);
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_user ON book_spells(user_id)`);
|
||||||
|
|
||||||
|
// Create series tables (v3)
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS book_series (series_id TEXT PRIMARY KEY, user_id TEXT NOT NULL, name TEXT NOT NULL, hashed_name TEXT NOT NULL, description TEXT, cover_image TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (user_id) REFERENCES erit_users(user_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_book_series_user ON book_series(user_id)`);
|
||||||
|
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_books (series_id TEXT NOT NULL, book_id TEXT NOT NULL, book_order INTEGER NOT NULL DEFAULT 1, last_update INTEGER DEFAULT 0, PRIMARY KEY (series_id, book_id), FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE, FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_books_book ON series_books(book_id)`);
|
||||||
|
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_characters (character_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, first_name TEXT NOT NULL, last_name TEXT, nickname TEXT, age TEXT, gender TEXT, species TEXT, nationality TEXT, status TEXT, category TEXT NOT NULL, title TEXT, image TEXT, role TEXT, biography TEXT, history TEXT, speech_pattern TEXT, catchphrase TEXT, residence TEXT, notes TEXT, color TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_characters_series ON series_characters(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_characters_user ON series_characters(user_id)`);
|
||||||
|
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_characters_attributes (attr_id TEXT PRIMARY KEY, character_id TEXT NOT NULL, user_id TEXT NOT NULL, attribute_name TEXT NOT NULL, attribute_value TEXT NOT NULL, last_update INTEGER DEFAULT 0, FOREIGN KEY (character_id) REFERENCES series_characters(character_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_char_attrs_character ON series_characters_attributes(character_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_char_attrs_user ON series_characters_attributes(user_id)`);
|
||||||
|
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_worlds (world_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT NOT NULL, hashed_name TEXT NOT NULL, history TEXT, politics TEXT, economy TEXT, religion TEXT, languages TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_worlds_series ON series_worlds(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_worlds_user ON series_worlds(user_id)`);
|
||||||
|
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_world_elements (element_id TEXT PRIMARY KEY, world_id TEXT NOT NULL, user_id TEXT NOT NULL, element_type INTEGER NOT NULL, name TEXT NOT NULL, original_name TEXT NOT NULL, description TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (world_id) REFERENCES series_worlds(world_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_world_elements_world ON series_world_elements(world_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_world_elements_user ON series_world_elements(user_id)`);
|
||||||
|
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_locations (loc_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, loc_name TEXT NOT NULL, loc_original_name TEXT NOT NULL, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_locations_series ON series_locations(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_locations_user ON series_locations(user_id)`);
|
||||||
|
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_location_elements (element_id TEXT PRIMARY KEY, location_id TEXT NOT NULL, user_id TEXT NOT NULL, element_name TEXT NOT NULL, original_name TEXT NOT NULL, element_description TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (location_id) REFERENCES series_locations(loc_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_elements_location ON series_location_elements(location_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_elements_user ON series_location_elements(user_id)`);
|
||||||
|
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_location_sub_elements (sub_element_id TEXT PRIMARY KEY, element_id TEXT NOT NULL, user_id TEXT NOT NULL, sub_elem_name TEXT NOT NULL, original_name TEXT NOT NULL, sub_elem_description TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (element_id) REFERENCES series_location_elements(element_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_element ON series_location_sub_elements(element_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_user ON series_location_sub_elements(user_id)`);
|
||||||
|
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_spells (spell_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT NOT NULL, name_hash TEXT NOT NULL, description TEXT NOT NULL, appearance TEXT NOT NULL, tags TEXT, power_level TEXT, components TEXT, limitations TEXT, notes TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_series ON series_spells(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_user ON series_spells(user_id)`);
|
||||||
|
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_spell_tags (tag_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT NOT NULL, hashed_name TEXT NOT NULL, color TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spell_tags_series ON series_spell_tags(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spell_tags_user ON series_spell_tags(user_id)`);
|
||||||
|
|
||||||
|
// Add series_*_id columns to existing book tables (v3)
|
||||||
|
addColumn(db, 'book_characters', 'series_character_id', 'TEXT DEFAULT NULL');
|
||||||
|
addColumn(db, 'book_world', 'series_world_id', 'TEXT DEFAULT NULL');
|
||||||
|
addColumn(db, 'book_location', 'series_location_id', 'TEXT DEFAULT NULL');
|
||||||
|
addColumn(db, 'book_spells', 'series_spell_id', 'TEXT DEFAULT NULL');
|
||||||
|
|
||||||
// Drop old schema version table
|
// Drop old schema version table
|
||||||
db.exec('DROP TABLE IF EXISTS _schema_version');
|
db.exec('DROP TABLE IF EXISTS _schema_version');
|
||||||
}
|
}
|
||||||
@@ -236,6 +285,70 @@ export function runMigrations(db: Database): void {
|
|||||||
addColumn(db, 'book_characters', 'color', 'TEXT DEFAULT NULL');
|
addColumn(db, 'book_characters', 'color', 'TEXT DEFAULT NULL');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// v3 - Add series tables and series_*_id columns to existing book tables
|
||||||
|
if (currentVersion < 3) {
|
||||||
|
// Create series tables first (order matters for foreign keys)
|
||||||
|
|
||||||
|
// Book Series (main series table)
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS book_series (series_id TEXT PRIMARY KEY, user_id TEXT NOT NULL, name TEXT NOT NULL, hashed_name TEXT NOT NULL, description TEXT, cover_image TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (user_id) REFERENCES erit_users(user_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_book_series_user ON book_series(user_id)`);
|
||||||
|
|
||||||
|
// Series Books (link series to books with order)
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_books (series_id TEXT NOT NULL, book_id TEXT NOT NULL, book_order INTEGER NOT NULL DEFAULT 1, last_update INTEGER DEFAULT 0, PRIMARY KEY (series_id, book_id), FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE, FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_books_book ON series_books(book_id)`);
|
||||||
|
|
||||||
|
// Series Characters
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_characters (character_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, first_name TEXT NOT NULL, last_name TEXT, nickname TEXT, age TEXT, gender TEXT, species TEXT, nationality TEXT, status TEXT, category TEXT NOT NULL, title TEXT, image TEXT, role TEXT, biography TEXT, history TEXT, speech_pattern TEXT, catchphrase TEXT, residence TEXT, notes TEXT, color TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_characters_series ON series_characters(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_characters_user ON series_characters(user_id)`);
|
||||||
|
|
||||||
|
// Series Characters Attributes
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_characters_attributes (attr_id TEXT PRIMARY KEY, character_id TEXT NOT NULL, user_id TEXT NOT NULL, attribute_name TEXT NOT NULL, attribute_value TEXT NOT NULL, last_update INTEGER DEFAULT 0, FOREIGN KEY (character_id) REFERENCES series_characters(character_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_char_attrs_character ON series_characters_attributes(character_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_char_attrs_user ON series_characters_attributes(user_id)`);
|
||||||
|
|
||||||
|
// Series Worlds
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_worlds (world_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT NOT NULL, hashed_name TEXT NOT NULL, history TEXT, politics TEXT, economy TEXT, religion TEXT, languages TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_worlds_series ON series_worlds(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_worlds_user ON series_worlds(user_id)`);
|
||||||
|
|
||||||
|
// Series World Elements
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_world_elements (element_id TEXT PRIMARY KEY, world_id TEXT NOT NULL, user_id TEXT NOT NULL, element_type INTEGER NOT NULL, name TEXT NOT NULL, original_name TEXT NOT NULL, description TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (world_id) REFERENCES series_worlds(world_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_world_elements_world ON series_world_elements(world_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_world_elements_user ON series_world_elements(user_id)`);
|
||||||
|
|
||||||
|
// Series Locations
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_locations (loc_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, loc_name TEXT NOT NULL, loc_original_name TEXT NOT NULL, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_locations_series ON series_locations(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_locations_user ON series_locations(user_id)`);
|
||||||
|
|
||||||
|
// Series Location Elements
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_location_elements (element_id TEXT PRIMARY KEY, location_id TEXT NOT NULL, user_id TEXT NOT NULL, element_name TEXT NOT NULL, original_name TEXT NOT NULL, element_description TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (location_id) REFERENCES series_locations(loc_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_elements_location ON series_location_elements(location_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_elements_user ON series_location_elements(user_id)`);
|
||||||
|
|
||||||
|
// Series Location Sub Elements
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_location_sub_elements (sub_element_id TEXT PRIMARY KEY, element_id TEXT NOT NULL, user_id TEXT NOT NULL, sub_elem_name TEXT NOT NULL, original_name TEXT NOT NULL, sub_elem_description TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (element_id) REFERENCES series_location_elements(element_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_element ON series_location_sub_elements(element_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_user ON series_location_sub_elements(user_id)`);
|
||||||
|
|
||||||
|
// Series Spells
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_spells (spell_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT NOT NULL, name_hash TEXT NOT NULL, description TEXT NOT NULL, appearance TEXT NOT NULL, tags TEXT, power_level TEXT, components TEXT, limitations TEXT, notes TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_series ON series_spells(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_user ON series_spells(user_id)`);
|
||||||
|
|
||||||
|
// Series Spell Tags
|
||||||
|
db.exec(`CREATE TABLE IF NOT EXISTS series_spell_tags (tag_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT NOT NULL, hashed_name TEXT NOT NULL, color TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spell_tags_series ON series_spell_tags(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spell_tags_user ON series_spell_tags(user_id)`);
|
||||||
|
|
||||||
|
// Add series_*_id columns to existing book tables
|
||||||
|
addColumn(db, 'book_characters', 'series_character_id', 'TEXT DEFAULT NULL');
|
||||||
|
addColumn(db, 'book_world', 'series_world_id', 'TEXT DEFAULT NULL');
|
||||||
|
addColumn(db, 'book_location', 'series_location_id', 'TEXT DEFAULT NULL');
|
||||||
|
addColumn(db, 'book_spells', 'series_spell_id', 'TEXT DEFAULT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
setDbVersion(db, schemaVersion);
|
setDbVersion(db, schemaVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,8 +504,10 @@ export function initializeSchema(db: Database): void {
|
|||||||
residence TEXT,
|
residence TEXT,
|
||||||
notes TEXT,
|
notes TEXT,
|
||||||
color TEXT,
|
color TEXT,
|
||||||
|
series_character_id TEXT,
|
||||||
last_update INTEGER DEFAULT 0,
|
last_update INTEGER DEFAULT 0,
|
||||||
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (series_character_id) REFERENCES series_characters(character_id) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -478,8 +593,10 @@ export function initializeSchema(db: Database): void {
|
|||||||
user_id TEXT NOT NULL,
|
user_id TEXT NOT NULL,
|
||||||
loc_name TEXT NOT NULL,
|
loc_name TEXT NOT NULL,
|
||||||
loc_original_name TEXT NOT NULL,
|
loc_original_name TEXT NOT NULL,
|
||||||
|
series_location_id TEXT,
|
||||||
last_update INTEGER DEFAULT 0,
|
last_update INTEGER DEFAULT 0,
|
||||||
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (series_location_id) REFERENCES series_locations(loc_id) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -511,8 +628,10 @@ export function initializeSchema(db: Database): void {
|
|||||||
economy TEXT,
|
economy TEXT,
|
||||||
religion TEXT,
|
religion TEXT,
|
||||||
languages TEXT,
|
languages TEXT,
|
||||||
|
series_world_id TEXT,
|
||||||
last_update INTEGER DEFAULT 0,
|
last_update INTEGER DEFAULT 0,
|
||||||
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (series_world_id) REFERENCES series_worlds(world_id) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -696,11 +815,195 @@ export function initializeSchema(db: Database): void {
|
|||||||
components TEXT,
|
components TEXT,
|
||||||
limitations TEXT,
|
limitations TEXT,
|
||||||
notes TEXT,
|
notes TEXT,
|
||||||
|
series_spell_id TEXT,
|
||||||
last_update INTEGER DEFAULT 0,
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (series_spell_id) REFERENCES series_spells(spell_id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// SERIES TABLES
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
// Book Series (main series table)
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS book_series (
|
||||||
|
series_id TEXT PRIMARY KEY,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
hashed_name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
cover_image TEXT,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES erit_users(user_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Series Books (link series to books with order)
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS series_books (
|
||||||
|
series_id TEXT NOT NULL,
|
||||||
|
book_id TEXT NOT NULL,
|
||||||
|
book_order INTEGER NOT NULL DEFAULT 1,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
PRIMARY KEY (series_id, book_id),
|
||||||
|
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// Series Characters
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS series_characters (
|
||||||
|
character_id TEXT PRIMARY KEY,
|
||||||
|
series_id TEXT NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
first_name TEXT NOT NULL,
|
||||||
|
last_name TEXT,
|
||||||
|
nickname TEXT,
|
||||||
|
age TEXT,
|
||||||
|
gender TEXT,
|
||||||
|
species TEXT,
|
||||||
|
nationality TEXT,
|
||||||
|
status TEXT,
|
||||||
|
category TEXT NOT NULL,
|
||||||
|
title TEXT,
|
||||||
|
image TEXT,
|
||||||
|
role TEXT,
|
||||||
|
biography TEXT,
|
||||||
|
history TEXT,
|
||||||
|
speech_pattern TEXT,
|
||||||
|
catchphrase TEXT,
|
||||||
|
residence TEXT,
|
||||||
|
notes TEXT,
|
||||||
|
color TEXT,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Series Characters Attributes
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS series_characters_attributes (
|
||||||
|
attr_id TEXT PRIMARY KEY,
|
||||||
|
character_id TEXT NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
attribute_name TEXT NOT NULL,
|
||||||
|
attribute_value TEXT NOT NULL,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (character_id) REFERENCES series_characters(character_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Series Worlds
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS series_worlds (
|
||||||
|
world_id TEXT PRIMARY KEY,
|
||||||
|
series_id TEXT NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
hashed_name TEXT NOT NULL,
|
||||||
|
history TEXT,
|
||||||
|
politics TEXT,
|
||||||
|
economy TEXT,
|
||||||
|
religion TEXT,
|
||||||
|
languages TEXT,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Series World Elements
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS series_world_elements (
|
||||||
|
element_id TEXT PRIMARY KEY,
|
||||||
|
world_id TEXT NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
element_type INTEGER NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
original_name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (world_id) REFERENCES series_worlds(world_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Series Locations
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS series_locations (
|
||||||
|
loc_id TEXT PRIMARY KEY,
|
||||||
|
series_id TEXT NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
loc_name TEXT NOT NULL,
|
||||||
|
loc_original_name TEXT NOT NULL,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Series Location Elements
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS series_location_elements (
|
||||||
|
element_id TEXT PRIMARY KEY,
|
||||||
|
location_id TEXT NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
element_name TEXT NOT NULL,
|
||||||
|
original_name TEXT NOT NULL,
|
||||||
|
element_description TEXT,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (location_id) REFERENCES series_locations(loc_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Series Location Sub Elements
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS series_location_sub_elements (
|
||||||
|
sub_element_id TEXT PRIMARY KEY,
|
||||||
|
element_id TEXT NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
sub_elem_name TEXT NOT NULL,
|
||||||
|
original_name TEXT NOT NULL,
|
||||||
|
sub_elem_description TEXT,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (element_id) REFERENCES series_location_elements(element_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Series Spells
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS series_spells (
|
||||||
|
spell_id TEXT PRIMARY KEY,
|
||||||
|
series_id TEXT NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
name_hash TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL,
|
||||||
|
appearance TEXT NOT NULL,
|
||||||
|
tags TEXT,
|
||||||
|
power_level TEXT,
|
||||||
|
components TEXT,
|
||||||
|
limitations TEXT,
|
||||||
|
notes TEXT,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Series Spell Tags
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS series_spell_tags (
|
||||||
|
tag_id TEXT PRIMARY KEY,
|
||||||
|
series_id TEXT NOT NULL,
|
||||||
|
user_id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
hashed_name TEXT NOT NULL,
|
||||||
|
color TEXT,
|
||||||
|
last_update INTEGER DEFAULT 0,
|
||||||
|
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
// Create indexes for better performance
|
// Create indexes for better performance
|
||||||
createIndexes(db);
|
createIndexes(db);
|
||||||
}
|
}
|
||||||
@@ -724,6 +1027,28 @@ function createIndexes(db: Database): void {
|
|||||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_spell_tags_user ON book_spell_tags(user_id)`);
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spell_tags_user ON book_spell_tags(user_id)`);
|
||||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_book ON book_spells(book_id)`);
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_book ON book_spells(book_id)`);
|
||||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_user ON book_spells(user_id)`);
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_user ON book_spells(user_id)`);
|
||||||
|
|
||||||
|
// Series tables indexes
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_book_series_user ON book_series(user_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_books_book ON series_books(book_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_characters_series ON series_characters(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_characters_user ON series_characters(user_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_char_attrs_character ON series_characters_attributes(character_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_char_attrs_user ON series_characters_attributes(user_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_worlds_series ON series_worlds(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_worlds_user ON series_worlds(user_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_world_elements_world ON series_world_elements(world_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_world_elements_user ON series_world_elements(user_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_locations_series ON series_locations(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_locations_user ON series_locations(user_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_elements_location ON series_location_elements(location_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_elements_user ON series_location_elements(user_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_element ON series_location_sub_elements(element_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_user ON series_location_sub_elements(user_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_series ON series_spells(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_user ON series_spells(user_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spell_tags_series ON series_spell_tags(series_id)`);
|
||||||
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spell_tags_user ON series_spell_tags(user_id)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ interface AddWorldData {
|
|||||||
bookId: string;
|
bookId: string;
|
||||||
worldName: string;
|
worldName: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
seriesWorldId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddWorldElementData {
|
interface AddWorldElementData {
|
||||||
@@ -113,7 +114,7 @@ interface UpdateWorldData {
|
|||||||
|
|
||||||
interface UpdateBookToolData {
|
interface UpdateBookToolData {
|
||||||
bookId: string;
|
bookId: string;
|
||||||
toolName: 'characters' | 'worlds' | 'locations';
|
toolName: 'characters' | 'worlds' | 'locations' | 'spells';
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +341,7 @@ ipcMain.handle('db:book:worlds:get', createHandler<GetWorldsData, WorldListRespo
|
|||||||
// POST /book/world/add - Add world
|
// POST /book/world/add - Add world
|
||||||
ipcMain.handle('db:book:world:add', createHandler<AddWorldData, string>(
|
ipcMain.handle('db:book:world:add', createHandler<AddWorldData, string>(
|
||||||
function(userId: string, data: AddWorldData, lang: 'fr' | 'en') {
|
function(userId: string, data: AddWorldData, lang: 'fr' | 'en') {
|
||||||
return World.addNewWorld(userId, data.bookId, data.worldName, lang, data.id);
|
return World.addNewWorld(userId, data.bookId, data.worldName, lang, data.id, data.seriesWorldId || null);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ interface AddLocationSectionData {
|
|||||||
locationName: string;
|
locationName: string;
|
||||||
bookId: string;
|
bookId: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
seriesLocationId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddLocationElementData {
|
interface AddLocationElementData {
|
||||||
@@ -44,7 +45,7 @@ ipcMain.handle('db:location:all', createHandler<GetAllLocationsData, LocationLis
|
|||||||
// POST /location/section/add - Add location section
|
// POST /location/section/add - Add location section
|
||||||
ipcMain.handle('db:location:section:add', createHandler<AddLocationSectionData, string>(
|
ipcMain.handle('db:location:section:add', createHandler<AddLocationSectionData, string>(
|
||||||
function(userId: string, data: AddLocationSectionData, lang: 'fr' | 'en'): string {
|
function(userId: string, data: AddLocationSectionData, lang: 'fr' | 'en'): string {
|
||||||
return Location.addLocationSection(userId, data.locationName, data.bookId, lang, data.id);
|
return Location.addLocationSection(userId, data.locationName, data.bookId, lang, data.id, data.seriesLocationId || null);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -73,6 +74,19 @@ ipcMain.handle('db:location:update', createHandler<UpdateLocationData, UpdateLoc
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// POST /location/section/update - Update location section with series link
|
||||||
|
interface UpdateSectionWithSeriesLinkData {
|
||||||
|
sectionId: string;
|
||||||
|
sectionName?: string;
|
||||||
|
seriesLocationId?: string | null;
|
||||||
|
}
|
||||||
|
ipcMain.handle('db:location:section:update', createHandler<UpdateSectionWithSeriesLinkData, boolean>(
|
||||||
|
function(userId: string, data: UpdateSectionWithSeriesLinkData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return Location.updateSectionWithSeriesLink(userId, data.sectionId, data.sectionName, data.seriesLocationId, lang);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// DELETE /location/delete - Delete location section
|
// DELETE /location/delete - Delete location section
|
||||||
interface DeleteLocationData {
|
interface DeleteLocationData {
|
||||||
locationId: string;
|
locationId: string;
|
||||||
|
|||||||
83
electron/ipc/series-character.ipc.ts
Normal file
83
electron/ipc/series-character.ipc.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { createHandler } from '../database/LocalSystem.js';
|
||||||
|
import SeriesCharacter, { SeriesCharacterPropsPost, SeriesCharacterListProps, CharacterAttributesResponse } from '../database/models/SeriesCharacter.js';
|
||||||
|
|
||||||
|
interface GetCharacterListData {
|
||||||
|
seriesId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetCharacterAttributesData {
|
||||||
|
characterId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddCharacterData {
|
||||||
|
seriesId: string;
|
||||||
|
character: SeriesCharacterPropsPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateCharacterData {
|
||||||
|
character: SeriesCharacterPropsPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteCharacterData {
|
||||||
|
characterId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddAttributeData {
|
||||||
|
characterId: string;
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteAttributeData {
|
||||||
|
attributeId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /series/character/list - Get character list
|
||||||
|
ipcMain.handle('db:series:character:list', createHandler<GetCharacterListData, SeriesCharacterListProps[]>(
|
||||||
|
function(userId: string, data: GetCharacterListData, lang: 'fr' | 'en'): SeriesCharacterListProps[] {
|
||||||
|
return SeriesCharacter.getCharacterList(userId, data.seriesId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// GET /series/character/attribute - Get character attributes
|
||||||
|
ipcMain.handle('db:series:character:attributes', createHandler<GetCharacterAttributesData, CharacterAttributesResponse>(
|
||||||
|
function(userId: string, data: GetCharacterAttributesData, lang: 'fr' | 'en'): CharacterAttributesResponse {
|
||||||
|
return SeriesCharacter.getCharacterAttributes(userId, data.characterId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/character/add - Add new character
|
||||||
|
ipcMain.handle('db:series:character:add', createHandler<AddCharacterData, string>(
|
||||||
|
function(userId: string, data: AddCharacterData, lang: 'fr' | 'en'): string {
|
||||||
|
return SeriesCharacter.addNewCharacter(userId, data.character, data.seriesId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// PATCH /series/character/update - Update character
|
||||||
|
ipcMain.handle('db:series:character:update', createHandler<UpdateCharacterData, boolean>(
|
||||||
|
function(userId: string, data: UpdateCharacterData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesCharacter.updateCharacter(userId, data.character, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// DELETE /series/character/delete - Delete character
|
||||||
|
ipcMain.handle('db:series:character:delete', createHandler<DeleteCharacterData, boolean>(
|
||||||
|
function(userId: string, data: DeleteCharacterData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesCharacter.deleteCharacter(userId, data.characterId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/character/attribute/add - Add attribute
|
||||||
|
ipcMain.handle('db:series:character:attribute:add', createHandler<AddAttributeData, string>(
|
||||||
|
function(userId: string, data: AddAttributeData, lang: 'fr' | 'en'): string {
|
||||||
|
return SeriesCharacter.addNewAttribute(data.characterId, userId, data.type, data.name, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// DELETE /series/character/attribute/delete - Delete attribute
|
||||||
|
ipcMain.handle('db:series:character:attribute:delete', createHandler<DeleteAttributeData, boolean>(
|
||||||
|
function(userId: string, data: DeleteAttributeData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesCharacter.deleteAttribute(userId, data.attributeId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
85
electron/ipc/series-location.ipc.ts
Normal file
85
electron/ipc/series-location.ipc.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { createHandler } from '../database/LocalSystem.js';
|
||||||
|
import SeriesLocation, { SeriesLocationListProps } from '../database/models/SeriesLocation.js';
|
||||||
|
|
||||||
|
interface GetLocationListData {
|
||||||
|
seriesId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddLocationSectionData {
|
||||||
|
seriesId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddElementData {
|
||||||
|
locationId: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddSubElementData {
|
||||||
|
elementId: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteLocationData {
|
||||||
|
locationId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteElementData {
|
||||||
|
elementId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSubElementData {
|
||||||
|
subElementId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /series/location/list - Get location list
|
||||||
|
ipcMain.handle('db:series:location:list', createHandler<GetLocationListData, SeriesLocationListProps[]>(
|
||||||
|
function(userId: string, data: GetLocationListData, lang: 'fr' | 'en'): SeriesLocationListProps[] {
|
||||||
|
return SeriesLocation.getLocationList(userId, data.seriesId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/location/section/add - Add location section
|
||||||
|
ipcMain.handle('db:series:location:section:add', createHandler<AddLocationSectionData, string>(
|
||||||
|
function(userId: string, data: AddLocationSectionData, lang: 'fr' | 'en'): string {
|
||||||
|
return SeriesLocation.addLocationSection(userId, data.seriesId, data.name, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/location/element/add - Add element
|
||||||
|
ipcMain.handle('db:series:location:element:add', createHandler<AddElementData, string>(
|
||||||
|
function(userId: string, data: AddElementData, lang: 'fr' | 'en'): string {
|
||||||
|
return SeriesLocation.addElement(userId, data.locationId, data.name, lang, data.description);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/location/sub-element/add - Add sub-element
|
||||||
|
ipcMain.handle('db:series:location:subelement:add', createHandler<AddSubElementData, string>(
|
||||||
|
function(userId: string, data: AddSubElementData, lang: 'fr' | 'en'): string {
|
||||||
|
return SeriesLocation.addSubElement(userId, data.elementId, data.name, lang, data.description);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// DELETE /series/location/delete - Delete location
|
||||||
|
ipcMain.handle('db:series:location:delete', createHandler<DeleteLocationData, boolean>(
|
||||||
|
function(userId: string, data: DeleteLocationData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesLocation.deleteLocation(userId, data.locationId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// DELETE /series/location/element/delete - Delete element
|
||||||
|
ipcMain.handle('db:series:location:element:delete', createHandler<DeleteElementData, boolean>(
|
||||||
|
function(userId: string, data: DeleteElementData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesLocation.deleteElement(userId, data.elementId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// DELETE /series/location/sub-element/delete - Delete sub-element
|
||||||
|
ipcMain.handle('db:series:location:subelement:delete', createHandler<DeleteSubElementData, boolean>(
|
||||||
|
function(userId: string, data: DeleteSubElementData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesLocation.deleteSubElement(userId, data.subElementId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
111
electron/ipc/series-spell.ipc.ts
Normal file
111
electron/ipc/series-spell.ipc.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { createHandler } from '../database/LocalSystem.js';
|
||||||
|
import SeriesSpell, { SeriesSpellListResponse, SeriesSpellDetailProps } from '../database/models/SeriesSpell.js';
|
||||||
|
|
||||||
|
interface GetSpellListData {
|
||||||
|
seriesId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSpellDetailData {
|
||||||
|
spellId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddSpellData {
|
||||||
|
seriesId: string;
|
||||||
|
name: string;
|
||||||
|
description?: string | null;
|
||||||
|
appearance?: string | null;
|
||||||
|
tags?: string[];
|
||||||
|
powerLevel?: string | null;
|
||||||
|
components?: string | null;
|
||||||
|
limitations?: string | null;
|
||||||
|
notes?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSpellData {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string | null;
|
||||||
|
appearance?: string | null;
|
||||||
|
tags?: string[];
|
||||||
|
powerLevel?: string | null;
|
||||||
|
components?: string | null;
|
||||||
|
limitations?: string | null;
|
||||||
|
notes?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSpellData {
|
||||||
|
spellId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddTagData {
|
||||||
|
seriesId: string;
|
||||||
|
name: string;
|
||||||
|
color?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateTagData {
|
||||||
|
tagId: string;
|
||||||
|
name: string;
|
||||||
|
color?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteTagData {
|
||||||
|
tagId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /series/spell/list - Get spell list
|
||||||
|
ipcMain.handle('db:series:spell:list', createHandler<GetSpellListData, SeriesSpellListResponse>(
|
||||||
|
function(userId: string, data: GetSpellListData, lang: 'fr' | 'en'): SeriesSpellListResponse {
|
||||||
|
return SeriesSpell.getSpellList(userId, data.seriesId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// GET /series/spell/detail - Get spell detail
|
||||||
|
ipcMain.handle('db:series:spell:detail', createHandler<GetSpellDetailData, SeriesSpellDetailProps>(
|
||||||
|
function(userId: string, data: GetSpellDetailData, lang: 'fr' | 'en'): SeriesSpellDetailProps {
|
||||||
|
return SeriesSpell.getSpellDetail(userId, data.spellId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/spell/add - Add spell
|
||||||
|
ipcMain.handle('db:series:spell:add', createHandler<AddSpellData, string>(
|
||||||
|
function(userId: string, data: AddSpellData, lang: 'fr' | 'en'): string {
|
||||||
|
return SeriesSpell.addSpell(userId, data.seriesId, data.name, lang, data.description, data.appearance, data.tags, data.powerLevel, data.components, data.limitations, data.notes);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// PUT /series/spell/update - Update spell
|
||||||
|
ipcMain.handle('db:series:spell:update', createHandler<UpdateSpellData, boolean>(
|
||||||
|
function(userId: string, data: UpdateSpellData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesSpell.updateSpell(userId, data.id, data.name, lang, data.description, data.appearance, data.tags, data.powerLevel, data.components, data.limitations, data.notes);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// DELETE /series/spell/delete - Delete spell
|
||||||
|
ipcMain.handle('db:series:spell:delete', createHandler<DeleteSpellData, boolean>(
|
||||||
|
function(userId: string, data: DeleteSpellData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesSpell.deleteSpell(userId, data.spellId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/spell/tag/add - Add tag
|
||||||
|
ipcMain.handle('db:series:spell:tag:add', createHandler<AddTagData, string>(
|
||||||
|
function(userId: string, data: AddTagData, lang: 'fr' | 'en'): string {
|
||||||
|
return SeriesSpell.addTag(userId, data.seriesId, data.name, lang, data.color);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// PUT /series/spell/tag/update - Update tag
|
||||||
|
ipcMain.handle('db:series:spell:tag:update', createHandler<UpdateTagData, boolean>(
|
||||||
|
function(userId: string, data: UpdateTagData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesSpell.updateTag(userId, data.tagId, data.name, lang, data.color);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// DELETE /series/spell/tag/delete - Delete tag
|
||||||
|
ipcMain.handle('db:series:spell:tag:delete', createHandler<DeleteTagData, boolean>(
|
||||||
|
function(userId: string, data: DeleteTagData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesSpell.deleteTag(userId, data.tagId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
24
electron/ipc/series-sync.ipc.ts
Normal file
24
electron/ipc/series-sync.ipc.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { createHandler } from '../database/LocalSystem.js';
|
||||||
|
import SeriesSync, { SeriesSyncUploadPayload, SeriesSyncResult } from '../database/models/SeriesSync.js';
|
||||||
|
import { SyncElementType } from '../database/repositories/series-sync.repo.js';
|
||||||
|
|
||||||
|
interface UploadToSeriesData {
|
||||||
|
type: SyncElementType;
|
||||||
|
bookElementId: string;
|
||||||
|
field: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /series/sync/upload - Upload field to series
|
||||||
|
ipcMain.handle('db:series:sync:upload', createHandler<UploadToSeriesData, SeriesSyncResult>(
|
||||||
|
function(userId: string, data: UploadToSeriesData, lang: 'fr' | 'en'): SeriesSyncResult {
|
||||||
|
const payload: SeriesSyncUploadPayload = {
|
||||||
|
type: data.type,
|
||||||
|
bookElementId: data.bookElementId,
|
||||||
|
field: data.field,
|
||||||
|
value: data.value || ''
|
||||||
|
};
|
||||||
|
return SeriesSync.uploadFieldToSeries(userId, payload, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
76
electron/ipc/series-world.ipc.ts
Normal file
76
electron/ipc/series-world.ipc.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { createHandler } from '../database/LocalSystem.js';
|
||||||
|
import SeriesWorld, { SeriesWorldListProps, SeriesWorldUpdateProps } from '../database/models/SeriesWorld.js';
|
||||||
|
|
||||||
|
interface GetWorldListData {
|
||||||
|
seriesId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddWorldData {
|
||||||
|
seriesId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateWorldData {
|
||||||
|
worldId: string;
|
||||||
|
name: string;
|
||||||
|
history?: string;
|
||||||
|
politics?: string;
|
||||||
|
economy?: string;
|
||||||
|
religion?: string;
|
||||||
|
languages?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddElementData {
|
||||||
|
worldId: string;
|
||||||
|
elementType: number;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteElementData {
|
||||||
|
elementId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /series/world/list - Get world list
|
||||||
|
ipcMain.handle('db:series:world:list', createHandler<GetWorldListData, SeriesWorldListProps[]>(
|
||||||
|
function(userId: string, data: GetWorldListData, lang: 'fr' | 'en'): SeriesWorldListProps[] {
|
||||||
|
return SeriesWorld.getWorldList(userId, data.seriesId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/world/add - Add world
|
||||||
|
ipcMain.handle('db:series:world:add', createHandler<AddWorldData, string>(
|
||||||
|
function(userId: string, data: AddWorldData, lang: 'fr' | 'en'): string {
|
||||||
|
return SeriesWorld.addWorld(userId, data.seriesId, data.name, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// PATCH /series/world/update - Update world
|
||||||
|
ipcMain.handle('db:series:world:update', createHandler<UpdateWorldData, boolean>(
|
||||||
|
function(userId: string, data: UpdateWorldData, lang: 'fr' | 'en'): boolean {
|
||||||
|
const worldData: SeriesWorldUpdateProps = {
|
||||||
|
name: data.name,
|
||||||
|
history: data.history,
|
||||||
|
politics: data.politics,
|
||||||
|
economy: data.economy,
|
||||||
|
religion: data.religion,
|
||||||
|
languages: data.languages
|
||||||
|
};
|
||||||
|
return SeriesWorld.updateWorld(userId, data.worldId, worldData, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/world/element/add - Add element
|
||||||
|
ipcMain.handle('db:series:world:element:add', createHandler<AddElementData, string>(
|
||||||
|
function(userId: string, data: AddElementData, lang: 'fr' | 'en'): string {
|
||||||
|
return SeriesWorld.addElement(userId, data.worldId, data.elementType, data.name, lang, data.description);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// DELETE /series/world/element/delete - Delete element
|
||||||
|
ipcMain.handle('db:series:world:element:delete', createHandler<DeleteElementData, boolean>(
|
||||||
|
function(userId: string, data: DeleteElementData, lang: 'fr' | 'en'): boolean {
|
||||||
|
return SeriesWorld.deleteElement(userId, data.elementId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
117
electron/ipc/series.ipc.ts
Normal file
117
electron/ipc/series.ipc.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { createHandler } from '../database/LocalSystem.js';
|
||||||
|
import Series, { BooksOrderPost, SeriesDetailProps, SeriesListItemProps, SeriesBookProps } from '../database/models/Series.js';
|
||||||
|
|
||||||
|
interface CreateSeriesData {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
bookIds?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSeriesData {
|
||||||
|
seriesId: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSeriesData {
|
||||||
|
seriesId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSeriesDetailData {
|
||||||
|
seriesId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AddBookToSeriesData {
|
||||||
|
seriesId: string;
|
||||||
|
bookId: string;
|
||||||
|
order?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoveBookFromSeriesData {
|
||||||
|
seriesId: string;
|
||||||
|
bookId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateBooksOrderData {
|
||||||
|
seriesId: string;
|
||||||
|
booksOrder: BooksOrderPost[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSeriesForBookData {
|
||||||
|
bookId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSeriesBooksData {
|
||||||
|
seriesId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /series/list - Get all series
|
||||||
|
ipcMain.handle('db:series:list', createHandler<void, SeriesListItemProps[]>(
|
||||||
|
async function(userId: string, _body: void, lang: 'fr' | 'en'): Promise<SeriesListItemProps[]> {
|
||||||
|
return await Series.getSeriesList(userId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// GET /series/detail - Get series detail
|
||||||
|
ipcMain.handle('db:series:detail', createHandler<GetSeriesDetailData, SeriesDetailProps>(
|
||||||
|
async function(userId: string, data: GetSeriesDetailData, lang: 'fr' | 'en'): Promise<SeriesDetailProps> {
|
||||||
|
return await Series.getSeriesDetail(userId, data.seriesId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/add - Create new series
|
||||||
|
ipcMain.handle('db:series:create', createHandler<CreateSeriesData, string>(
|
||||||
|
async function(userId: string, data: CreateSeriesData, lang: 'fr' | 'en'): Promise<string> {
|
||||||
|
return await Series.createSeries(userId, data.name, data.description || '', lang, data.bookIds);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// PUT /series/update - Update series
|
||||||
|
ipcMain.handle('db:series:update', createHandler<UpdateSeriesData, boolean>(
|
||||||
|
async function(userId: string, data: UpdateSeriesData, lang: 'fr' | 'en'): Promise<boolean> {
|
||||||
|
return await Series.updateSeries(userId, data.seriesId, data.name, data.description || '', lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// DELETE /series/delete - Delete series
|
||||||
|
ipcMain.handle('db:series:delete', createHandler<DeleteSeriesData, boolean>(
|
||||||
|
async function(userId: string, data: DeleteSeriesData, lang: 'fr' | 'en'): Promise<boolean> {
|
||||||
|
return await Series.deleteSeries(userId, data.seriesId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// GET /series/book/list - Get books in series
|
||||||
|
ipcMain.handle('db:series:books', createHandler<GetSeriesBooksData, SeriesBookProps[]>(
|
||||||
|
async function(userId: string, data: GetSeriesBooksData, lang: 'fr' | 'en'): Promise<SeriesBookProps[]> {
|
||||||
|
return await Series.getSeriesBooks(userId, data.seriesId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// POST /series/book/add - Add book to series
|
||||||
|
ipcMain.handle('db:series:book:add', createHandler<AddBookToSeriesData, boolean>(
|
||||||
|
async function(userId: string, data: AddBookToSeriesData, lang: 'fr' | 'en'): Promise<boolean> {
|
||||||
|
return await Series.addBookToSeries(userId, data.seriesId, data.bookId, data.order ?? 1, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// DELETE /series/book/remove - Remove book from series
|
||||||
|
ipcMain.handle('db:series:book:remove', createHandler<RemoveBookFromSeriesData, boolean>(
|
||||||
|
async function(userId: string, data: RemoveBookFromSeriesData, lang: 'fr' | 'en'): Promise<boolean> {
|
||||||
|
return await Series.removeBookFromSeries(userId, data.seriesId, data.bookId, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// PUT /series/book/reorder - Reorder books in series
|
||||||
|
ipcMain.handle('db:series:book:reorder', createHandler<UpdateBooksOrderData, boolean>(
|
||||||
|
async function(userId: string, data: UpdateBooksOrderData, lang: 'fr' | 'en'): Promise<boolean> {
|
||||||
|
return await Series.updateBooksOrder(userId, data.seriesId, data.booksOrder, lang);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// GET /series/for-book - Get series ID for a book
|
||||||
|
ipcMain.handle('db:series:forBook', createHandler<GetSeriesForBookData, string | null>(
|
||||||
|
function(_userId: string, data: GetSeriesForBookData, _lang: 'fr' | 'en'): string | null {
|
||||||
|
return Series.getSeriesIdForBook(data.bookId);
|
||||||
|
}
|
||||||
|
));
|
||||||
@@ -19,6 +19,7 @@ interface SpellPost {
|
|||||||
components?: string | null;
|
components?: string | null;
|
||||||
limitations?: string | null;
|
limitations?: string | null;
|
||||||
notes?: string | null;
|
notes?: string | null;
|
||||||
|
seriesSpellId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetSpellListData {
|
interface GetSpellListData {
|
||||||
@@ -115,6 +116,7 @@ ipcMain.handle(
|
|||||||
spell.notes || null,
|
spell.notes || null,
|
||||||
spell.id,
|
spell.id,
|
||||||
lang,
|
lang,
|
||||||
|
spell.seriesSpellId || null,
|
||||||
);
|
);
|
||||||
return result.id;
|
return result.id;
|
||||||
},
|
},
|
||||||
@@ -139,6 +141,7 @@ ipcMain.handle(
|
|||||||
spell.limitations || null,
|
spell.limitations || null,
|
||||||
spell.notes || null,
|
spell.notes || null,
|
||||||
lang,
|
lang,
|
||||||
|
spell.seriesSpellId || null,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user