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

554 lines
28 KiB
TypeScript

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.");
}
}
}
/**
* Checks if a series exists for a user (alias for isSeriesExist).
* @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 seriesExists(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): boolean {
return this.isSeriesExist(userId, seriesId, lang);
}
}