- Replaced `id` with `bookId` in `BookProps` and updated corresponding references. - Added `tools` property to book-related components for tool management. - Removed unused `books` property from `User` model. - Added debug logs in `deleteBook` and `BookList` for troubleshooting.
473 lines
18 KiB
TypeScript
473 lines
18 KiB
TypeScript
import System from '../System.js';
|
|
import { getUserEncryptionKey } from '../keyManager.js';
|
|
import BookRepo, { BookQuery, BookToolsTable, BookToolsSettings, EritBooksTable } from "../repositories/book.repository.js";
|
|
import { BookActSummariesTable } from "../repositories/act.repository.js";
|
|
import { BookAIGuideLineTable, BookGuideLineTable } from "../repositories/guideline.repository.js";
|
|
import ChapterRepo, {
|
|
BookChapterInfosTable,
|
|
BookChaptersTable,
|
|
ChapterBookResult
|
|
} from "../repositories/chapter.repository.js";
|
|
import { BookChapterContentTable } from "../repositories/chaptercontent.repository.js";
|
|
import {
|
|
BookCharactersAttributesTable,
|
|
BookCharactersTable
|
|
} from "../repositories/character.repository.js";
|
|
import { BookIncidentsTable } from "../repositories/incident.repository.js";
|
|
import { BookIssuesTable } from "../repositories/issue.repository.js";
|
|
import {
|
|
BookLocationTable,
|
|
LocationElementTable,
|
|
LocationSubElementTable
|
|
} from "../repositories/location.repository.js";
|
|
import { BookPlotPointsTable } from "../repositories/plotpoint.repository.js";
|
|
import { BookWorldElementsTable, BookWorldTable } from "../repositories/world.repository.js";
|
|
import { CompleteChapterContent, SyncedChapter } from "./Chapter.js";
|
|
import { SyncedCharacter } from "./Character.js";
|
|
import { SyncedLocation } from "./Location.js";
|
|
import { SyncedWorld } from "./World.js";
|
|
import { SyncedIncident } from "./Incident.js";
|
|
import { SyncedPlotPoint } from "./PlotPoint.js";
|
|
import { SyncedIssue } from "./Issue.js";
|
|
import { SyncedActSummary } from "./Act.js";
|
|
import { SyncedAIGuideLine, SyncedGuideLine } from "./GuideLine.js";
|
|
import Cover from "./Cover.js";
|
|
import UserRepo from "../repositories/user.repository.js";
|
|
|
|
export interface SyncedBookTools {
|
|
charactersEnabled: boolean;
|
|
worldsEnabled: boolean;
|
|
locationsEnabled: boolean;
|
|
}
|
|
|
|
export interface BookProps {
|
|
bookId: string;
|
|
type: string;
|
|
authorId: string;
|
|
title: string;
|
|
subTitle?: string;
|
|
summary?: string;
|
|
serieId?: number;
|
|
desiredReleaseDate?: string;
|
|
desiredWordCount?: number;
|
|
wordCount?: number;
|
|
coverImage?: string;
|
|
bookMeta?: string;
|
|
tools?: BookToolsSettings;
|
|
}
|
|
|
|
export interface CompleteBook {
|
|
eritBooks: EritBooksTable[];
|
|
actSummaries: BookActSummariesTable[];
|
|
aiGuideLine: BookAIGuideLineTable[];
|
|
chapters: BookChaptersTable[];
|
|
chapterContents: BookChapterContentTable[];
|
|
chapterInfos: BookChapterInfosTable[];
|
|
characters: BookCharactersTable[];
|
|
characterAttributes: BookCharactersAttributesTable[];
|
|
guideLine: BookGuideLineTable[];
|
|
incidents: BookIncidentsTable[];
|
|
issues: BookIssuesTable[];
|
|
locations: BookLocationTable[];
|
|
plotPoints: BookPlotPointsTable[];
|
|
worlds: BookWorldTable[];
|
|
worldElements: BookWorldElementsTable[];
|
|
locationElements: LocationElementTable[];
|
|
locationSubElements: LocationSubElementTable[];
|
|
bookTools: BookToolsTable[];
|
|
}
|
|
|
|
export interface SyncedBook {
|
|
id: string;
|
|
type: string;
|
|
title: string;
|
|
subTitle: string | null;
|
|
lastUpdate: number;
|
|
chapters: SyncedChapter[];
|
|
characters: SyncedCharacter[];
|
|
locations: SyncedLocation[];
|
|
worlds: SyncedWorld[];
|
|
incidents: SyncedIncident[];
|
|
plotPoints: SyncedPlotPoint[];
|
|
issues: SyncedIssue[];
|
|
actSummaries: SyncedActSummary[];
|
|
guideLine: SyncedGuideLine | null;
|
|
aiGuideLine: SyncedAIGuideLine | null;
|
|
bookTools: SyncedBookTools | null;
|
|
}
|
|
|
|
export interface BookSyncCompare {
|
|
id: string;
|
|
chapters: string[];
|
|
chapterContents: string[];
|
|
chapterInfos: string[];
|
|
characters: string[];
|
|
characterAttributes: string[];
|
|
locations: string[];
|
|
locationElements: string[];
|
|
locationSubElements: string[];
|
|
worlds: string[];
|
|
worldElements: string[];
|
|
incidents: string[];
|
|
plotPoints: string[];
|
|
issues: string[];
|
|
actSummaries: string[];
|
|
guideLine: boolean;
|
|
aiGuideLine: boolean;
|
|
bookTools: boolean;
|
|
}
|
|
|
|
export interface CompleteBookData {
|
|
bookId: string;
|
|
title: string;
|
|
subTitle: string;
|
|
summary: string;
|
|
coverImage: string;
|
|
userInfos: {
|
|
firstName: string;
|
|
lastName: string;
|
|
authorName: string;
|
|
},
|
|
chapters: CompleteChapterContent[];
|
|
}
|
|
|
|
export default class Book {
|
|
private readonly id: string;
|
|
private type: string;
|
|
private authorId: string;
|
|
private title: string;
|
|
private subTitle: string;
|
|
private summary: string;
|
|
private serieId: number;
|
|
private desiredReleaseDate: string;
|
|
private desiredWordCount: number;
|
|
private wordCount: number;
|
|
private cover: string;
|
|
|
|
/**
|
|
* Creates a new Book instance.
|
|
* @param id - The unique identifier of the book
|
|
* @param authorId - The unique identifier of the author (optional)
|
|
*/
|
|
constructor(id: string, authorId?: string) {
|
|
this.id = id;
|
|
if (authorId) {
|
|
this.authorId = authorId;
|
|
} else {
|
|
this.authorId = '';
|
|
}
|
|
this.title = '';
|
|
this.subTitle = '';
|
|
this.summary = '';
|
|
this.serieId = 0;
|
|
this.desiredReleaseDate = '';
|
|
this.desiredWordCount = 0;
|
|
this.wordCount = 0;
|
|
this.cover = '';
|
|
this.type = '';
|
|
}
|
|
|
|
/**
|
|
* Retrieves all books for a specific user.
|
|
* @param userId - The unique identifier of the user
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
* @returns A promise resolving to an array of book properties
|
|
* @throws Error if the user encryption key is not found
|
|
*/
|
|
public static async getBooks(userId: string, lang: 'fr' | 'en' = 'fr'): Promise<BookProps[]> {
|
|
const userKey: string | null = getUserEncryptionKey(userId);
|
|
if (!userKey) {
|
|
throw new Error(
|
|
lang === 'fr' ? "Clé d'encryption utilisateur non trouvée." : 'User encryption key not found.'
|
|
);
|
|
}
|
|
|
|
const books: BookQuery[] = BookRepo.fetchBooks(userId, lang);
|
|
if (!books || books.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
return await Promise.all(
|
|
books.map(async (book: BookQuery): Promise<BookProps> => {
|
|
return {
|
|
bookId: book.book_id,
|
|
type: book.type,
|
|
authorId: book.author_id,
|
|
title: System.decryptDataWithUserKey(book.title, userKey),
|
|
subTitle: book.sub_title ? System.decryptDataWithUserKey(book.sub_title, userKey) : '',
|
|
summary: book.summary ? System.decryptDataWithUserKey(book.summary, userKey) : '',
|
|
serieId: book.serie_id || 0,
|
|
desiredReleaseDate: book.desired_release_date || '',
|
|
desiredWordCount: book.desired_word_count || 0,
|
|
wordCount: book.words_count || 0,
|
|
coverImage: book.cover_image ? Cover.getPicture(userId, userKey, book.cover_image) : '',
|
|
};
|
|
}) ?? []
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Adds a new book to the database.
|
|
* @param bookId - The unique identifier for the book (optional, will be generated if null)
|
|
* @param userId - The unique identifier of the user
|
|
* @param title - The title of the book
|
|
* @param subTitle - The subtitle of the book
|
|
* @param summary - The summary of the book
|
|
* @param type - The type/genre of the book
|
|
* @param serie - The series identifier
|
|
* @param publicationDate - The desired publication date
|
|
* @param desiredWordCount - The target word count
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
* @returns A promise resolving to the book ID
|
|
* @throws Error if a book with the same title already exists
|
|
*/
|
|
public static async addBook(bookId: string | null, userId: string, title: string, subTitle: string, summary: string, type: string, serie: number, publicationDate: string, desiredWordCount: number, lang: 'fr' | 'en' = 'fr'): Promise<string> {
|
|
let newBookId: string = '';
|
|
const userKey: string | null = getUserEncryptionKey(userId);
|
|
|
|
const encryptedTitle: string = System.encryptDataWithUserKey(title, userKey);
|
|
const encryptedSubTitle: string = subTitle ? System.encryptDataWithUserKey(subTitle, userKey) : '';
|
|
const encryptedSummary: string = summary ? System.encryptDataWithUserKey(summary, userKey) : '';
|
|
const hashedTitle: string = System.hashElement(title);
|
|
const hashedSubTitle: string = subTitle ? System.hashElement(subTitle) : '';
|
|
|
|
if (BookRepo.verifyBookExist(hashedTitle, hashedSubTitle, userId, lang)) {
|
|
throw new Error(lang === "fr" ? `Tu as déjà un livre intitulé ${title} - ${subTitle}.` : `You already have a book named ${title} - ${subTitle}.`);
|
|
}
|
|
if (bookId) {
|
|
newBookId = bookId;
|
|
} else {
|
|
newBookId = System.createUniqueId();
|
|
}
|
|
return BookRepo.insertBook(newBookId, userId, encryptedTitle, hashedTitle, encryptedSubTitle, hashedSubTitle, encryptedSummary, type, serie, publicationDate, desiredWordCount, lang);
|
|
}
|
|
|
|
/**
|
|
* Retrieves a single book by its ID.
|
|
* @param userId - The unique identifier of the user
|
|
* @param bookId - The unique identifier of the book
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
* @returns A promise resolving to the book properties
|
|
*/
|
|
public static async getBook(userId: string, bookId: string, lang: 'fr' | 'en'): Promise<BookProps> {
|
|
const book: Book = new Book(bookId);
|
|
book.getBookInfos(userId);
|
|
const bookTools: BookToolsTable | null = BookRepo.fetchBookTools(userId, bookId, lang);
|
|
return {
|
|
bookId: book.getId(),
|
|
type: book.getType(),
|
|
authorId: book.getAuthorId(),
|
|
title: book.getTitle(),
|
|
subTitle: book.getSubTitle(),
|
|
summary: book.getSummary(),
|
|
serieId: book.getSerieId(),
|
|
desiredReleaseDate: book.getDesiredReleaseDate(),
|
|
desiredWordCount: book.getDesiredWordCount(),
|
|
wordCount: book.getWordCount(),
|
|
coverImage: book.getCover(),
|
|
tools: {
|
|
characters: bookTools ? bookTools.characters_enabled === 1 : false,
|
|
worlds: bookTools ? bookTools.worlds_enabled === 1 : false,
|
|
locations: bookTools ? bookTools.locations_enabled === 1 : false
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Updates basic information for a book.
|
|
* @param userId - The unique identifier of the user
|
|
* @param title - The new title
|
|
* @param subTitle - The new subtitle
|
|
* @param summary - The new summary
|
|
* @param publicationDate - The new publication date
|
|
* @param wordCount - The new word count
|
|
* @param bookId - The unique identifier of the book
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
* @returns True if the update was successful, false otherwise
|
|
*/
|
|
static updateBookBasicInformation(userId: string, title: string, subTitle: string, summary: string, publicationDate: string, wordCount: number, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
|
const userKey: string = getUserEncryptionKey(userId);
|
|
const encryptedTitle: string = System.encryptDataWithUserKey(title, userKey);
|
|
const encryptedSubTitle: string = subTitle ? System.encryptDataWithUserKey(subTitle, userKey) : '';
|
|
const encryptedSummary: string = summary ? System.encryptDataWithUserKey(summary, userKey) : '';
|
|
const hashedTitle: string = System.hashElement(title);
|
|
const hashedSubTitle: string = subTitle ? System.hashElement(subTitle) : '';
|
|
return BookRepo.updateBookBasicInformation(userId, encryptedTitle, hashedTitle, encryptedSubTitle, hashedSubTitle, encryptedSummary, publicationDate, wordCount, bookId, lang);
|
|
}
|
|
|
|
/**
|
|
* Removes a book from the database.
|
|
* @param userId - The unique identifier of the user
|
|
* @param bookId - The unique identifier of the book to remove
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
* @returns True if the book was removed, false otherwise
|
|
*/
|
|
public static removeBook(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
|
return BookRepo.deleteBook(userId, bookId, lang);
|
|
}
|
|
|
|
public static updateBookToolSetting(userId: string, bookId: string, toolName: 'characters' | 'worlds' | 'locations', enabled: boolean, lang: 'fr' | 'en' = 'fr'): boolean {
|
|
const columnName: 'characters_enabled' | 'worlds_enabled' | 'locations_enabled' = `${toolName}_enabled` as 'characters_enabled' | 'worlds_enabled' | 'locations_enabled';
|
|
return BookRepo.updateBookToolSetting(userId, bookId, columnName, enabled, lang);
|
|
}
|
|
|
|
/**
|
|
* Gets the book ID.
|
|
* @returns The book's unique identifier
|
|
*/
|
|
public getId(): string {
|
|
return this.id;
|
|
}
|
|
|
|
/**
|
|
* Gets the author ID.
|
|
* @returns The author's unique identifier
|
|
*/
|
|
public getAuthorId(): string {
|
|
return this.authorId;
|
|
}
|
|
|
|
/**
|
|
* Gets the book title.
|
|
* @returns The decrypted book title
|
|
*/
|
|
public getTitle(): string {
|
|
return this.title;
|
|
}
|
|
|
|
/**
|
|
* Gets the book subtitle.
|
|
* @returns The decrypted book subtitle
|
|
*/
|
|
public getSubTitle(): string {
|
|
return this.subTitle;
|
|
}
|
|
|
|
/**
|
|
* Gets the book summary.
|
|
* @returns The decrypted book summary
|
|
*/
|
|
public getSummary(): string {
|
|
return this.summary;
|
|
}
|
|
|
|
/**
|
|
* Gets the series ID.
|
|
* @returns The series identifier
|
|
*/
|
|
public getSerieId(): number {
|
|
return this.serieId;
|
|
}
|
|
|
|
/**
|
|
* Gets the desired release date.
|
|
* @returns The desired release date string
|
|
*/
|
|
public getDesiredReleaseDate(): string {
|
|
return this.desiredReleaseDate;
|
|
}
|
|
|
|
/**
|
|
* Gets the desired word count.
|
|
* @returns The target word count
|
|
*/
|
|
public getDesiredWordCount(): number {
|
|
return this.desiredWordCount;
|
|
}
|
|
|
|
/**
|
|
* Gets the current word count.
|
|
* @returns The current word count
|
|
*/
|
|
public getWordCount(): number {
|
|
return this.wordCount;
|
|
}
|
|
|
|
/**
|
|
* Gets the cover image.
|
|
* @returns The cover image data
|
|
*/
|
|
public getCover(): string {
|
|
return this.cover;
|
|
}
|
|
|
|
/**
|
|
* Gets the book type.
|
|
* @returns The book type/genre
|
|
*/
|
|
public getType(): string {
|
|
return this.type;
|
|
}
|
|
|
|
/**
|
|
* Retrieves complete book data including chapters and user information.
|
|
* @param userId - The unique identifier of the user
|
|
* @param id - The unique identifier of the book
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
* @returns The complete book data with decrypted content
|
|
*/
|
|
static completeBookData(userId: string, id: string, lang: 'fr' | 'en' = 'fr'): CompleteBookData {
|
|
const bookData: BookQuery = BookRepo.fetchBook(id, userId, lang);
|
|
const chapters: ChapterBookResult[] = ChapterRepo.fetchCompleteBookChapters(id, lang);
|
|
const userKey: string = getUserEncryptionKey(userId);
|
|
const userInfos = UserRepo.fetchAccountInformation(userId, lang);
|
|
|
|
const bookTitle: string = bookData.title ? System.decryptDataWithUserKey(bookData.title, userKey) : '';
|
|
const decryptedChapters: CompleteChapterContent[] = [];
|
|
|
|
for (const chapter of chapters) {
|
|
decryptedChapters.push({
|
|
id: '',
|
|
title: chapter.title ? System.decryptDataWithUserKey(chapter.title, userKey) : '',
|
|
content: chapter.content ? System.decryptDataWithUserKey(chapter.content, userKey) : '',
|
|
order: chapter.chapter_order
|
|
})
|
|
}
|
|
const coverImage: string = bookData.cover_image ? Cover.getPicture(userId, userKey, bookData.cover_image, lang) : '';
|
|
return {
|
|
bookId: id,
|
|
title: bookTitle,
|
|
subTitle: bookData.sub_title ? System.decryptDataWithUserKey(bookData.sub_title, userKey) : '',
|
|
summary: bookData.summary ? System.decryptDataWithUserKey(bookData.summary, userKey) : '',
|
|
coverImage: coverImage,
|
|
userInfos: {
|
|
firstName: userInfos.first_name ? System.decryptDataWithUserKey(userInfos.first_name, userKey) : '',
|
|
lastName: userInfos.last_name ? System.decryptDataWithUserKey(userInfos.last_name, userKey) : '',
|
|
authorName: userInfos.author_name ? System.decryptDataWithUserKey(userInfos.author_name, userKey) : '',
|
|
},
|
|
chapters: decryptedChapters
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Loads book information from the database into the instance.
|
|
* @param userId - The unique identifier of the user
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
*/
|
|
public getBookInfos(userId: string, lang: 'fr' | 'en' = 'fr'): void {
|
|
const bookData: BookQuery = BookRepo.fetchBook(this.id, userId, lang);
|
|
const userKey: string = getUserEncryptionKey(userId);
|
|
if (bookData) {
|
|
this.authorId = bookData.author_id;
|
|
this.type = bookData.type;
|
|
this.title = bookData.title ? System.decryptDataWithUserKey(bookData.title, userKey) : '';
|
|
this.subTitle = bookData.sub_title ? System.decryptDataWithUserKey(bookData.sub_title, userKey) : '';
|
|
this.summary = bookData.summary ? System.decryptDataWithUserKey(bookData.summary, userKey) : '';
|
|
this.serieId = bookData.serie_id ?? 0;
|
|
this.desiredReleaseDate = bookData.desired_release_date ?? '';
|
|
this.desiredWordCount = bookData.desired_word_count ?? 0;
|
|
this.wordCount = bookData.words_count ?? 0;
|
|
this.cover = bookData.cover_image ? Cover.getPicture(userId, userKey, bookData.cover_image, lang) : '';
|
|
} else {
|
|
this.authorId = '';
|
|
this.title = '';
|
|
this.subTitle = '';
|
|
this.summary = '';
|
|
this.serieId = 0;
|
|
this.desiredReleaseDate = '';
|
|
this.wordCount = 0;
|
|
this.cover = '';
|
|
}
|
|
}
|
|
}
|