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:
natreex
2026-01-26 19:57:56 -05:00
parent 2359638cb0
commit cec5830360
35 changed files with 5483 additions and 203 deletions

View File

@@ -58,6 +58,10 @@ export interface BookToolsTable extends Record<string, SQLiteValue> {
export interface SyncedBookToolsResult extends Record<string, SQLiteValue> {
last_update: number;
characters_enabled: number;
worlds_enabled: number;
locations_enabled: number;
spells_enabled: number;
}
export default class BookRepo {
@@ -440,7 +444,7 @@ export default class BookRepo {
static fetchSyncedBookTools(userId: string, bookId: string, lang: 'fr' | 'en'): SyncedBookToolsResult | null {
try {
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 result = db.get(query, params) as SyncedBookToolsResult | undefined;
return result ?? null;

View File

@@ -63,8 +63,7 @@ export default class ChapterContentRepository {
LIMIT 1
`;
const params: SQLiteValue[] = [userId, bookId];
const chapterContents: ChapterContentQueryResult[] = db.all(query, params) as ChapterContentQueryResult[];
return chapterContents;
return db.all(query, params) as ChapterContentQueryResult[];
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`DB Error: ${error.message}`);
@@ -127,8 +126,7 @@ export default class ChapterContentRepository {
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 params: SQLiteValue[] = [userId, chapterId, version];
const companionContents: CompanionContentQueryResult[] = db.all(query, params) as CompanionContentQueryResult[];
return companionContents;
return db.all(query, params) as CompanionContentQueryResult[];
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`DB Error: ${error.message}`);

View File

@@ -54,23 +54,24 @@ export interface CharacterResult extends Record<string, SQLiteValue> {
character_id: string;
first_name: string;
last_name: string;
nickname: string;
age: string;
gender: string;
species: string;
nationality: string;
status: 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;
catchphrase: string;
residence: string;
notes: string;
color: string;
speech_pattern: string | null;
catchphrase: string | null;
residence: string | null;
notes: string | null;
color: string | null;
series_character_id: string | null;
}
export interface AttributeResult extends Record<string, SQLiteValue> {
@@ -83,11 +84,22 @@ export interface CompleteCharacterResult 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;
category: string;
title: string;
role: string;
biography: string;
history: string;
speech_pattern: string | null;
catchphrase: string | null;
residence: string | null;
notes: string | null;
color: string | null;
attribute_name: string;
attribute_value: string;
}
@@ -103,7 +115,7 @@ export default class CharacterRepo {
public static fetchCharacters(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): CharacterResult[] {
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 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 characters: CharacterResult[] = db.all(query, params) as CharacterResult[];
return characters;
@@ -129,46 +141,35 @@ export default class CharacterRepo {
*/
public static addNewCharacter(userId: string, characterId: string, characterData: {
firstName: string;
lastName: string;
nickname: string;
age: string;
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;
}, bookId: string, lang: 'fr' | 'en' = 'fr'): string {
lastName: 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 | 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;
}, bookId: string, lang: 'fr' | 'en' = 'fr', seriesCharacterId: string | null = null): string {
let insertResult: RunResult;
try {
const db: Database = System.getDb();
const query: string = `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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
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, 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;
const query: string = seriesCharacterId
? '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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'
: '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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)';
const params: SQLiteValue[] = seriesCharacterId
? [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, 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()];
insertResult = db.run(query, params);
} catch (error: unknown) {
if (error instanceof Error) {
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.");
}
}
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
*/
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 `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 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 de l'attribut.` : `Error adding attribute.`);
}
return attributeId;
insertResult = db.run(query, params);
} catch (error: unknown) {
if (error instanceof Error) {
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.");
}
}
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: {
firstName: string;
lastName: string;
nickname: string;
age: string;
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;
}, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
lastName: 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 | 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', seriesCharacterId: string | null = null): boolean {
try {
const db: Database = System.getDb();
const query: string = `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=?`;
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
];
const query: string = seriesCharacterId !== null
? '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=?'
: '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=?';
const params: SQLiteValue[] = seriesCharacterId !== null
? [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]
: [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);
return updateResult.changes > 0;
} catch (error: unknown) {
@@ -357,7 +355,7 @@ export default class CharacterRepo {
static fetchCompleteCharacters(userId: string, bookId: string, tags: string[], lang: 'fr' | 'en' = 'fr'): CompleteCharacterResult[] {
try {
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];
if (tags && tags.length > 0) {
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 {
try {
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 updateResult: RunResult = db.run(query, params);
return updateResult.changes > 0;

View File

@@ -10,6 +10,7 @@ export interface LocationQueryResult extends Record<string, SQLiteValue> {
sub_element_id: string;
sub_elem_name: string;
sub_elem_description: string;
series_location_id: string | null;
}
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[] {
try {
const db: Database = System.getDb();
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
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 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 = ?';
const params: SQLiteValue[] = [userId, bookId];
const locations: LocationQueryResult[] = db.all(query, params) as LocationQueryResult[];
return locations;
@@ -122,19 +115,17 @@ export default class LocationRepo {
* @param lang - The language for error messages ('fr' or 'en')
* @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 {
const db: Database = System.getDb();
const query: string = `
INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, last_update)
VALUES (?, ?, ?, ?, ?, ?)
`;
const params: SQLiteValue[] = [locationId, bookId, userId, encryptedName, originalName, 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 de la section d'emplacement.` : `Error adding location section.`);
}
return locationId;
const query: string = seriesLocationId
? 'INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, series_location_id, last_update) VALUES (?, ?, ?, ?, ?, ?, ?)'
: 'INSERT INTO book_location (loc_id, book_id, user_id, loc_name, loc_original_name, last_update) VALUES (?, ?, ?, ?, ?, ?)';
const params: SQLiteValue[] = seriesLocationId
? [locationId, bookId, userId, encryptedName, originalName, seriesLocationId, System.timeStampInSeconds()]
: [locationId, bookId, userId, encryptedName, originalName, System.timeStampInSeconds()];
insertResult = db.run(query, params);
} catch (error: unknown) {
if (error instanceof Error) {
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.");
}
}
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
*/
static insertLocationElement(userId: string, elementId: string, locationId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): string {
let insertResult: RunResult;
try {
const db: Database = System.getDb();
const query: string = `
INSERT INTO location_element (element_id, location, user_id, element_name, original_name, element_description, last_update)
VALUES (?, ?, ?, ?, ?, ?, ?)
`;
const query: string = '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 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 de l'élément d'emplacement.` : `Error adding location element.`);
}
return elementId;
insertResult = db.run(query, params);
} catch (error: unknown) {
if (error instanceof Error) {
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.");
}
}
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
*/
static insertLocationSubElement(userId: string, subElementId: string, elementId: string, encryptedName: string, originalName: string, lang: 'fr' | 'en' = 'fr'): string {
let insertResult: RunResult;
try {
const db: Database = System.getDb();
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 (?, ?, ?, ?, ?, ?, ?)
`;
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 (?, ?, ?, ?, ?, ?, ?)';
const params: SQLiteValue[] = [subElementId, elementId, userId, encryptedName, originalName, '', 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 sous-élément d'emplacement.` : `Error adding location sub-element.`);
}
return subElementId;
insertResult = db.run(query, params);
} catch (error: unknown) {
if (error instanceof Error) {
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.");
}
}
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.");
}
}
}
}

View 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.");
}
}
}
}

View 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.");
}
}
}
}

View 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.");
}
}
}
}

View 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.");
}
}
}

View 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.");
}
}
}
}

View 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.");
}
}
}
}

View File

@@ -12,6 +12,7 @@ export interface SpellResult extends Record<string, SQLiteValue> {
components: string | null;
limitations: string | null;
notes: string | null;
series_spell_id: string | null;
}
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[] {
try {
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];
return db.all(query, params) as SpellResult[];
} catch (error: unknown) {
@@ -71,7 +72,7 @@ export default class SpellRepo {
static fetchSpellById(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): SpellResult | null {
try {
const db: Database = System.getDb();
const query: string = 'SELECT spell_id, book_id, name, description, appearance, tags, power_level, components, limitations, notes 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 spells: SpellResult[] = db.all(query, params) as SpellResult[];
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')
* @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 {
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 params: SQLiteValue[] = [spellId, bookId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, System.timeStampInSeconds()];
const result: RunResult = db.run(query, params);
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;
const query: string = seriesSpellId
? 'INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, series_spell_id, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)'
: 'INSERT INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)';
const params: SQLiteValue[] = seriesSpellId
? [spellId, bookId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, seriesSpellId, System.timeStampInSeconds()]
: [spellId, bookId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, System.timeStampInSeconds()];
result = db.run(query, params);
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`[SpellRepo] DB Error: ${error.message}`);
@@ -120,6 +122,10 @@ export default class SpellRepo {
}
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')
* @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 {
const db: Database = System.getDb();
const query: string = 'UPDATE book_spells SET name=?, name_hash=?, description=?, appearance=?, tags=?, power_level=?, components=?, limitations=?, notes=?, last_update=? WHERE spell_id=? AND user_id=?';
const params: SQLiteValue[] = [name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, System.timeStampInSeconds(), spellId, userId];
const query: string = seriesSpellId !== null
? 'UPDATE book_spells SET name=?, name_hash=?, description=?, appearance=?, tags=?, power_level=?, components=?, limitations=?, notes=?, series_spell_id=?, last_update=? WHERE spell_id=? AND user_id=?'
: 'UPDATE book_spells SET name=?, name_hash=?, description=?, appearance=?, tags=?, power_level=?, components=?, limitations=?, notes=?, last_update=? WHERE spell_id=? AND user_id=?';
const params: SQLiteValue[] = seriesSpellId !== null
? [name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, seriesSpellId, System.timeStampInSeconds(), spellId, userId]
: [name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, System.timeStampInSeconds(), spellId, userId];
const result: RunResult = db.run(query, params);
return result.changes > 0;
} catch (error: unknown) {

View File

@@ -62,15 +62,12 @@ export default class SpellTagRepo {
* @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 {
let result: RunResult;
try {
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 params: SQLiteValue[] = [tagId, bookId, userId, name, nameHash, color, System.timeStampInSeconds()];
const result: RunResult = 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;
result = db.run(query, params);
} catch (error: unknown) {
if (error instanceof Error) {
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.`);
}
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;
}
/**

View File

@@ -52,6 +52,7 @@ export interface WorldQuery extends Record<string, SQLiteValue> {
element_name: string | null;
element_description: string | null;
element_type: number | null;
series_world_id: string | null;
}
export interface WorldElementValue {
@@ -98,12 +99,16 @@ export default class WorldRepository {
* @param lang - The language for error messages ('fr' or 'en')
* @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;
try {
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 params: SQLiteValue[] = [worldId, userId, bookId, encryptedName, hashedName, System.timeStampInSeconds()];
const query: string = seriesWorldId
? '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);
} catch (error: unknown) {
if (error instanceof Error) {
@@ -130,7 +135,7 @@ export default class WorldRepository {
public static fetchWorlds(userId: string, bookId: string, lang: 'fr' | 'en'): WorldQuery[] {
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 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 worlds: WorldQuery[] = db.all(query, params) as WorldQuery[];
return worlds;
@@ -160,11 +165,15 @@ export default class WorldRepository {
* @param lang - The language for error messages ('fr' or 'en')
* @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 {
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 params: SQLiteValue[] = [encryptName, hashedName, encryptHistory, encryptPolitics, encryptEconomy, encryptReligion, encryptLanguages, lastUpdate, userId, worldId];
const query: string = seriesWorldId !== null
? '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);
return updateResult.changes > 0;
} catch (error: unknown) {