Files
ERitors-Scribe-Desktop/electron/database/models/Series.ts
natreex cec5830360 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.
2026-01-26 19:57:56 -05:00

249 lines
10 KiB
TypeScript

import { getUserEncryptionKey } from "../keyManager.js";
import System from "../System.js";
import SeriesRepo, { SeriesBookResult, SeriesListItem, SeriesResult } from "../repositories/series.repo.js";
export interface SeriesProps {
id: string;
name: string;
description: string;
coverImage: string | null;
}
export interface SeriesDetailProps {
id: string;
name: string;
description: string;
coverImage: string | null;
books: SeriesBookProps[];
}
export interface SeriesBookProps {
bookId: string;
title: string;
order: number;
coverImage: string | null;
}
export interface SeriesListItemProps {
id: string;
name: string;
description: string;
coverImage: string | null;
bookCount: number;
bookIds: string[];
}
export interface BooksOrderPost {
bookId: string;
order: number;
}
export default class Series {
/**
* Gets the list of all series for a user.
* @param userId - The unique identifier of the user
* @param lang - The language for error messages ('fr' or 'en')
* @returns The list of series with decrypted names and descriptions
*/
public static getSeriesList(userId: string, lang: 'fr' | 'en' = 'fr'): SeriesListItemProps[] {
const userKey: string = getUserEncryptionKey(userId);
const seriesResults: SeriesListItem[] = SeriesRepo.fetchUserSeries(userId, lang);
return seriesResults.map((seriesItem: SeriesListItem): SeriesListItemProps => ({
id: seriesItem.series_id,
name: System.decryptDataWithUserKey(seriesItem.name, userKey),
description: seriesItem.description ? System.decryptDataWithUserKey(seriesItem.description, userKey) : '',
coverImage: seriesItem.cover_image,
bookCount: seriesItem.book_count,
bookIds: seriesItem.book_ids ? seriesItem.book_ids.split(',') : []
}));
}
/**
* Gets the detail of a series including its books.
* @param userId - The unique identifier of the user
* @param seriesId - The unique identifier of the series
* @param lang - The language for error messages ('fr' or 'en')
* @returns The series detail with decrypted data
*/
public static getSeriesDetail(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesDetailProps {
const userKey: string = getUserEncryptionKey(userId);
const seriesResult: SeriesResult | null = SeriesRepo.fetchSeriesById(userId, seriesId, lang);
if (!seriesResult) {
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
}
const booksResult: SeriesBookResult[] = SeriesRepo.fetchSeriesBooks(userId, seriesId, lang);
const books: SeriesBookProps[] = booksResult.map((book: SeriesBookResult) => ({
bookId: book.book_id,
title: System.decryptDataWithUserKey(book.title, userKey),
order: book.book_order,
coverImage: book.cover_image
}));
return {
id: seriesResult.series_id,
name: System.decryptDataWithUserKey(seriesResult.name, userKey),
description: seriesResult.description ? System.decryptDataWithUserKey(seriesResult.description, userKey) : '',
coverImage: seriesResult.cover_image,
books
};
}
/**
* Creates a new series.
* @param userId - The unique identifier of the user
* @param name - The name of the series
* @param description - The description of the series
* @param lang - The language for error messages ('fr' or 'en')
* @param bookIds - Optional array of book IDs to add to the series
* @returns The created series ID
*/
public static createSeries(userId: string, name: string, description: string, lang: 'fr' | 'en' = 'fr', bookIds?: string[]): string {
const userKey: string = getUserEncryptionKey(userId);
const seriesId: string = System.createUniqueId();
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
const hashedName: string = System.hashElement(name);
const encryptedDescription: string | null = description ? System.encryptDataWithUserKey(description, userKey) : null;
SeriesRepo.insertSeries(seriesId, userId, encryptedName, hashedName, encryptedDescription, lang);
if (bookIds && bookIds.length > 0) {
for (let i: number = 0; i < bookIds.length; i++) {
SeriesRepo.addBookToSeries(seriesId, bookIds[i], i + 1, lang);
}
}
return seriesId;
}
/**
* Updates an existing series.
* @param userId - The unique identifier of the user
* @param seriesId - The unique identifier of the series
* @param name - The name of the series
* @param description - The description of the series
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if the update was successful
*/
public static updateSeries(userId: string, seriesId: string, name: string, description: string, lang: 'fr' | 'en' = 'fr'): boolean {
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
if (!exists) {
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
}
const userKey: string = getUserEncryptionKey(userId);
const encryptedName: string = System.encryptDataWithUserKey(name, userKey);
const hashedName: string = System.hashElement(name);
const encryptedDescription: string | null = description ? System.encryptDataWithUserKey(description, userKey) : null;
return SeriesRepo.updateSeries(userId, seriesId, encryptedName, hashedName, encryptedDescription, lang);
}
/**
* Deletes a series.
* @param userId - The unique identifier of the user
* @param seriesId - The unique identifier of the series
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if the deletion was successful
*/
public static deleteSeries(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): boolean {
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
if (!exists) {
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
}
return SeriesRepo.deleteSeries(userId, seriesId, lang);
}
/**
* Adds a book to a series.
* @param userId - The unique identifier of the user
* @param seriesId - The unique identifier of the series
* @param bookId - The unique identifier of the book
* @param order - The order of the book in the series
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if the addition was successful
*/
public static addBookToSeries(userId: string, seriesId: string, bookId: string, order: number, lang: 'fr' | 'en' = 'fr'): boolean {
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
if (!exists) {
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
}
return SeriesRepo.addBookToSeries(seriesId, bookId, order, lang);
}
/**
* Removes a book from a series.
* @param userId - The unique identifier of the user
* @param seriesId - The unique identifier of the series
* @param bookId - The unique identifier of the book
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if the removal was successful
*/
public static removeBookFromSeries(userId: string, seriesId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
if (!exists) {
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
}
return SeriesRepo.removeBookFromSeries(seriesId, bookId, lang);
}
/**
* Updates the order of books in a series.
* @param userId - The unique identifier of the user
* @param seriesId - The unique identifier of the series
* @param booksOrder - An array of {bookId, order} objects
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if the update was successful
*/
public static updateBooksOrder(userId: string, seriesId: string, booksOrder: BooksOrderPost[], lang: 'fr' | 'en' = 'fr'): boolean {
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
if (!exists) {
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
}
return SeriesRepo.updateBooksOrder(seriesId, booksOrder, lang);
}
/**
* Gets the series ID for a book if it belongs to one.
* @param bookId - The unique identifier of the book
* @returns The series ID or null
*/
public static getSeriesIdForBook(bookId: string): string | null {
return SeriesRepo.getSeriesIdForBook(bookId);
}
/**
* Gets only the books of a series (without series details).
* @param userId - The unique identifier of the user
* @param seriesId - The unique identifier of the series
* @param lang - The language for error messages ('fr' or 'en')
* @returns The list of books in the series
*/
public static getSeriesBooks(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesBookProps[] {
const userKey: string = getUserEncryptionKey(userId);
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
if (!exists) {
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
}
const booksResult: SeriesBookResult[] = SeriesRepo.fetchSeriesBooks(userId, seriesId, lang);
return booksResult.map((book: SeriesBookResult): SeriesBookProps => ({
bookId: book.book_id,
title: System.decryptDataWithUserKey(book.title, userKey),
order: book.book_order,
coverImage: book.cover_image
}));
}
}