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 })); } }