diff --git a/electron/database/models/Book.ts b/electron/database/models/Book.ts new file mode 100644 index 0000000..41e8882 --- /dev/null +++ b/electron/database/models/Book.ts @@ -0,0 +1,934 @@ +import BookRepository, { + BookCoverQuery, + BookQuery, + ChapterBookResult, GuideLineAIQuery, + GuideLineQuery, WorldElementValue +} from '../repositories/book.repository.js'; +import type { + IssueQuery, + ActQuery, + PlotPointQuery, + IncidentQuery, + WorldQuery +} from '../repositories/book.repository.js'; +import System from '../System.js'; +import { getUserEncryptionKey } from '../keyManager.js'; +import path from "path"; +import fs from "fs"; +import BookRepo from "../repositories/book.repository.js"; +import Chapter, {ActChapter, ChapterContentData, ChapterProps} from "./Chapter.js"; +import UserRepo from "../repositories/user.repository.js"; +import ChapterRepo from "@/electron/database/repositories/chapter.repository"; +import {mainStyle} from "@/electron/database/models/EpubStyle"; + +export interface BookProps{ + id:string; + type:string; + authorId:string; + title:string; + subTitle?:string; + summary?:string; + serieId?:number; + desiredReleaseDate?:string; + desiredWordCount?:number; + wordCount?:number; + coverImage?:string; + bookMeta?:string; +} + +export interface GuideLine{ + tone:string; + atmosphere:string; + writingStyle:string; + themes:string; + symbolism: string; + motifs: string; + narrativeVoice: string; + pacing: string; + intendedAudience: string; + keyMessages: string; +} + +interface PlotPoint { + plotPointId: string, + title:string, + summary:string, + linkedIncidentId: string | null, + chapters?:ActChapter[] +} + +interface Incident { + incidentId: string, + title:string, + summary:string, + chapters?:ActChapter[] +} + +export interface ExportData { + buffer: Buffer; + fileName: string; +} + +export interface Act { + id: number; + summary: string | null; + incidents?:Incident[]; + plotPoints?:PlotPoint[], + chapters?:ActChapter[] +} + +export interface Issue { + id: string, + name: string +} + +export interface WorldElement { + id: string; + name: string; + description: string; + type?:number; +} + +export interface WorldProps { + id: string; + name: string; + history: string; + politics: string; + economy: string; + religion: string; + languages: string; + laws: WorldElement[]; + biomes: WorldElement[]; + issues: WorldElement[]; + customs: WorldElement[]; + kingdoms: WorldElement[]; + climate: WorldElement[]; + resources: WorldElement[]; + wildlife: WorldElement[]; + arts: WorldElement[]; + ethnicGroups: WorldElement[]; + socialClasses: WorldElement[]; + importantCharacters: WorldElement[]; +} + +export interface GuideLineAI { + narrativeType: number|null; + dialogueType: number|null; + globalResume: string|null; + atmosphere: string|null; + verbeTense: number|null; + langue: number|null; + currentResume: string|null; + themes: string|null; +} + +export interface CompleteBookData { + bookId: string; + title: string; + subTitle: string; + summary: string; + coverImage: string; + userInfos: { + firstName: string; + lastName: string; + authorName: string; + }, + chapters: CompleteChapterContent[]; +} + +export interface CompleteChapterContent { + id: string; + title: string; + content: string; + order: number; + version?: number; +} + +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; + + 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 = ''; + } + public static async getBooks(userId: string, lang: 'fr' | 'en' = 'fr'): Promise { + 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[] = BookRepository.fetchBooks(userId, lang); + if (!books || books.length === 0) { + return []; + } + + return await Promise.all( + books.map(async (book: BookQuery):Promise => { + return { + id: 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 ? await this.getPicture(userId, userKey, book.cover_image) : '', + }; + }) ?? [] + ); + } + + public static async getCoverPicture(userId:string,bookId:string, lang: 'fr' | 'en' = 'fr'):Promise{ + const query:BookCoverQuery = BookRepo.fetchBookCover(userId,bookId,lang); + if (query){ + const userKey:string = getUserEncryptionKey(userId); + return System.decryptDataWithUserKey(query.cover_image,userKey); + } else { + return ''; + } + } + + public static async deleteCoverPicture(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): Promise { + const coverName:string = await Book.getCoverPicture(userId,bookId,lang); + return BookRepo.updateBookCover(bookId, '', userId, lang); + } + public static getPicture(userId: string, userKey: string, image: string, lang: 'fr' | 'en' = 'fr'): string { + if (!image) return ''; + try { + const decryptedFileName: string = System.decryptDataWithUserKey(image, userKey); + const userDirectory: string = path.join(''); + fs.accessSync(userDirectory, fs.constants.R_OK); + const data = fs.readFileSync(userDirectory); + return data.toString('base64'); + } catch (err) { + return ''; + } + } + + 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 { + let id: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){ + id = bookId; + } else { + id = System.createUniqueId(); + } + return BookRepo.insertBook(id,userId,encryptedTitle,hashedTitle,encryptedSubTitle,hashedSubTitle,encryptedSummary,type,serie,publicationDate,desiredWordCount,lang); + } + + public static async getBook(userId:string,bookId: string, lang: 'fr' | 'en' = 'fr'): Promise { + const book:Book = new Book(bookId); + await book.getBookInfos(userId); + return { + id: 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() + }; + } + + public static async getGuideLine(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): Promise { + const guideLineResponse: GuideLineQuery[] = BookRepo.fetchGuideLine(userId, bookId,lang); + if (guideLineResponse.length === 0) { + return null; + } + const guideLine: GuideLineQuery = guideLineResponse[0]; + const userKey: string = getUserEncryptionKey(userId); + return { + tone: guideLine.tone ? System.decryptDataWithUserKey(guideLine.tone, userKey) : '', + atmosphere: guideLine.atmosphere ? System.decryptDataWithUserKey(guideLine.atmosphere, userKey) : '', + writingStyle: guideLine.writing_style ? System.decryptDataWithUserKey(guideLine.writing_style, userKey) : '', + themes: guideLine.themes ? System.decryptDataWithUserKey(guideLine.themes, userKey) : '', + symbolism: guideLine.symbolism ? System.decryptDataWithUserKey(guideLine.symbolism, userKey) : '', + motifs: guideLine.motifs ? System.decryptDataWithUserKey(guideLine.motifs, userKey) : '', + narrativeVoice: guideLine['narrative-voice'] ? System.decryptDataWithUserKey(guideLine['narrative-voice'] as string, userKey) : '', + pacing: guideLine.pacing ? System.decryptDataWithUserKey(guideLine.pacing, userKey) : '', + intendedAudience: guideLine.intended_audience ? System.decryptDataWithUserKey(guideLine.intended_audience, userKey) : '', + keyMessages: guideLine.key_messages ? System.decryptDataWithUserKey(guideLine.key_messages, userKey) : '', + }; + } + + public static async updateGuideLine(userId: string, bookId: string, tone: string | null, atmosphere: string | null, writingStyle: string | null, themes: string | null, symbolism: string | null, motifs: string | null, narrativeVoice: string | null, pacing: string | null, keyMessages: string | null, intendedAudience: string | null, lang: 'fr' | 'en' = 'fr'): Promise { + const userKey: string = getUserEncryptionKey(userId); + const encryptedTone: string = tone ? System.encryptDataWithUserKey(tone, userKey) : ''; + const encryptedAtmosphere: string = atmosphere ? System.encryptDataWithUserKey(atmosphere, userKey) : ''; + const encryptedWritingStyle: string = writingStyle ? System.encryptDataWithUserKey(writingStyle, userKey) : ''; + const encryptedThemes: string = themes ? System.encryptDataWithUserKey(themes, userKey) : ''; + const encryptedSymbolism: string = symbolism ? System.encryptDataWithUserKey(symbolism, userKey) : ''; + const encryptedMotifs: string = motifs ? System.encryptDataWithUserKey(motifs, userKey) : ''; + const encryptedNarrativeVoice: string = narrativeVoice ? System.encryptDataWithUserKey(narrativeVoice, userKey) : ''; + const encryptedPacing: string = pacing ? System.encryptDataWithUserKey(pacing, userKey) : ''; + const encryptedKeyMessages: string = keyMessages ? System.encryptDataWithUserKey(keyMessages, userKey) : ''; + const encryptedIntendedAudience: string = intendedAudience ? System.encryptDataWithUserKey(intendedAudience, userKey) : ''; + + return BookRepo.updateGuideLine(userId, bookId, encryptedTone, encryptedAtmosphere, encryptedWritingStyle, encryptedThemes, encryptedSymbolism, encryptedMotifs, encryptedNarrativeVoice, encryptedPacing, encryptedKeyMessages, encryptedIntendedAudience, lang); + } + + public static addNewIncident(userId: string, bookId: string, name: string, lang: 'fr' | 'en' = 'fr'): string { + const userKey: string = getUserEncryptionKey(userId); + const encryptedName:string = System.encryptDataWithUserKey(name,userKey); + const hashedName:string = System.hashElement(name); + const incidentId: string = System.createUniqueId(); + return BookRepo.insertNewIncident(incidentId, userId, bookId, encryptedName, hashedName, lang); + } + public static async getPlotPoints(userId:string, bookId: string,actChapters:ActChapter[], lang: 'fr' | 'en' = 'fr'):Promise{ + const query:PlotPointQuery[] = BookRepo.fetchAllPlotPoints(userId,bookId,lang); + const userKey:string = getUserEncryptionKey(userId); + let plotPoints:PlotPoint[] = []; + if (query.length>0){ + for (const plot of query) { + let chapters:ActChapter[] = []; + for (const chapter of actChapters) { + if (chapter.plotPointId === plot.plot_point_id){ + chapters.push(chapter); + } + } + plotPoints.push({ + plotPointId: plot.plot_point_id, + title: plot.title ? System.decryptDataWithUserKey(plot.title,userKey) : '', + summary : plot.summary ? System.decryptDataWithUserKey(plot.summary,userKey) : '', + linkedIncidentId: plot.linked_incident_id, + chapters:chapters + }) + } + } + return plotPoints; + } + public static async getIncitentsIncidents(userId:string,bookId: string,actChapters:ActChapter[], lang: 'fr' | 'en' = 'fr'):Promise{ + const query:IncidentQuery[] = BookRepo.fetchAllIncitentIncidents(userId,bookId,lang); + let incidents:Incident[] = []; + const userKey:string = getUserEncryptionKey(userId); + if (query.length>0){ + for (const incident of query) { + let chapters:ActChapter[] = []; + for (const chapter of actChapters) { + if (chapter.incidentId === incident.incident_id){ + chapters.push(chapter); + } + } + incidents.push({ + incidentId: incident.incident_id, + title: incident.title ? System.decryptDataWithUserKey(incident.title,userKey) : '', + summary : incident.summary ? System.decryptDataWithUserKey(incident.summary,userKey) : '', + chapters:chapters + }) + } + } + return incidents; + } + + public static removeIncident(userId: string, bookId: string, incidentId: string, lang: 'fr' | 'en' = 'fr'): boolean { + return BookRepo.deleteIncident(userId, bookId, incidentId, lang); + } + + public static async getActsData(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): Promise { + const userKey:string = getUserEncryptionKey(userId); + const actChapters:ActChapter[] = Chapter.getAllChapterFromActs(userId, bookId, lang); + const query: ActQuery[] = BookRepo.fetchAllActs(userId, bookId, lang); + const incidents: Incident[] = await Book.getIncitentsIncidents(userId, bookId, actChapters); + const plotsPoint: PlotPoint[] = await Book.getPlotPoints(userId, bookId, actChapters); + let acts: Act[] = []; + acts.push({ + id: 1, + summary: '', + chapters: actChapters.filter((chapter: ActChapter) => chapter.actId === 1) + }) + acts.push({ + id: 2, + summary: '', + incidents: incidents ? incidents : [], + }) + acts.push({ + id: 3, + summary: '', + plotPoints: plotsPoint ? plotsPoint : [], + }) + acts.push({ + id: 4, + summary: '', + chapters: actChapters.filter((chapter: ActChapter) => chapter.actId === 4) + }) + acts.push({ + id: 5, + summary: '', + chapters: actChapters.filter((chapter: ActChapter) => chapter.actId === 5) + }) + if (query.length > 0) { + for (const act of query) { + acts[act.act_index - 1].summary = act.summary && userKey ? System.decryptDataWithUserKey(act.summary, userKey) : ''; + } + } + return acts; + } + public static async getIssuesFromBook(userId:string,bookId:string, lang: 'fr' | 'en' = 'fr'):Promise{ + const query:IssueQuery[] = BookRepo.fetchIssuesFromBook(userId,bookId,lang); + const userKey:string = getUserEncryptionKey(userId); + let issues:Issue[] = []; + if (query.length>0){ + for (const issue of query) { + issues.push({ + id:issue.issue_id, + name: System.decryptDataWithUserKey(issue.name,userKey) + }) + } + } + return issues; + } + + 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); + } + + static addNewPlotPoint(userId: string, bookId: string, incidentId: string, name: string, lang: 'fr' | 'en' = 'fr'): string { + const userKey:string = getUserEncryptionKey(userId); + const encryptedName:string = System.encryptDataWithUserKey(name, userKey); + const hashedName:string = System.hashElement(name); + const plotPointId: string = System.createUniqueId(); + return BookRepo.insertNewPlotPoint(plotPointId, userId, bookId, encryptedName, hashedName, incidentId, lang); + } + + static removePlotPoint(userId: string, plotId: string, lang: 'fr' | 'en' = 'fr'): boolean{ + return BookRepo.deletePlotPoint(userId, plotId, lang); + } + + public static addNewIssue(userId: string, bookId: string, name: string, lang: 'fr' | 'en' = 'fr'): string { + const userKey:string = getUserEncryptionKey(userId); + const encryptedName:string = System.encryptDataWithUserKey(name,userKey); + const hashedName:string = System.hashElement(name); + const issueId: string = System.createUniqueId(); + return BookRepo.insertNewIssue(issueId, userId, bookId, encryptedName, hashedName,lang); + } + + public static removeIssue(userId: string, issueId: string, lang: 'fr' | 'en' = 'fr'): boolean{ + return BookRepo.deleteIssue(userId, issueId,lang); + } + + public static async updateAct(acts: Act[], userId: string, bookId: string, userKey: string, lang: 'fr' | 'en' = 'fr'): Promise { + for (const act of acts) { + const incidents: Incident[] = act.incidents ? act.incidents : []; + const actId: number = act.id; + if (actId === 1 || actId === 4 || actId === 5) { + const actSummary: string = act.summary ? System.encryptDataWithUserKey(act.summary, userKey) : ''; + try { + BookRepo.updateActSummary(userId, bookId, actId, actSummary,lang); + } catch (e: unknown) { + const actSummaryId: string = System.createUniqueId(); + BookRepo.insertActSummary(actSummaryId, userId, bookId, actId, actSummary,lang); + } + if (act.chapters) { + Chapter.updateChapterInfos(act.chapters, userId, actId, bookId, null, null, lang); + } + } else if (actId === 2) { + for (const incident of incidents) { + const incidentSummary: string = incident.summary ? System.encryptDataWithUserKey(incident.summary, userKey) : ''; + const incidentId: string = incident.incidentId; + const incidentName: string = incident.title; + const incidentHashedName: string = System.hashElement(incidentName); + const encryptedIncidentName: string = System.encryptDataWithUserKey(incidentName, userKey); + BookRepo.updateIncident(userId, bookId, incidentId, encryptedIncidentName, incidentHashedName, incidentSummary, lang); + if (incident.chapters) { + Chapter.updateChapterInfos(incident.chapters, userId, actId, bookId, incidentId, null, lang); + } + } + } else { + const plotPoints: PlotPoint[] = act.plotPoints ? act.plotPoints : []; + for (const plotPoint of plotPoints) { + const plotPointSummary: string = plotPoint.summary ? System.encryptDataWithUserKey(plotPoint.summary, userKey) : ''; + const plotPointId: string = plotPoint.plotPointId; + const plotPointName: string = plotPoint.title; + const plotPointHashedName: string = System.hashElement(plotPointName); + const encryptedPlotPointName: string = System.encryptDataWithUserKey(plotPointName, userKey); + BookRepo.updatePlotPoint(userId, bookId, plotPointId, encryptedPlotPointName, plotPointHashedName, plotPointSummary, lang); + if (plotPoint.chapters) { + Chapter.updateChapterInfos(plotPoint.chapters, userId, actId, bookId, null, plotPointId, lang); + } + } + } + } + return true; + } + + public static updateStory(userId: string, bookId: string, acts: Act[], mainChapters: ChapterProps[], lang: 'fr' | 'en' = 'fr'): boolean { + const userKey: string = getUserEncryptionKey(userId); + Book.updateAct(acts, userId, bookId, userKey, lang); + for (const chapter of mainChapters) { + const chapterId: string = chapter.chapterId; + const chapterTitle: string = chapter.title; + const chapterHashedTitle: string = System.hashElement(chapterTitle); + const encryptedTitle: string = System.encryptDataWithUserKey(chapterTitle, userKey); + const chapterOrder: number = chapter.chapterOrder; + ChapterRepo.updateChapter(userId, chapterId, encryptedTitle, chapterHashedTitle, chapterOrder, lang); + } + return true; + } + + public static addNewWorld(userId: string, bookId: string, worldName: string, lang: 'fr' | 'en' = 'fr'): string { + const userKey: string = getUserEncryptionKey(userId); + const hashedName: string = System.hashElement(worldName); + if (BookRepo.checkWorldExist(userId, bookId, hashedName, lang)) { + throw new Error(lang === "fr" ? `Tu as déjà un monde ${worldName}.` : `You already have a world named ${worldName}.`); + } + const encryptedName: string = System.encryptDataWithUserKey(worldName, userKey); + const worldId: string = System.createUniqueId(); + return BookRepo.insertNewWorld(worldId, userId, bookId, encryptedName, hashedName, lang); + } + + public static getWorlds(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): WorldProps[] { + const worldElements: WorldQuery[] = BookRepo.fetchWorlds(userId, bookId, lang); + const userKey: string = getUserEncryptionKey(userId); + let worlds: WorldProps[] = [] + for (const element of worldElements) { + const existWorld: WorldProps | undefined = worlds.find((world: WorldProps) => world.id === element.world_id); + + if (!existWorld) { + const newWorld: WorldProps = { + id: element.world_id, + name: System.decryptDataWithUserKey(element.world_name, userKey), + history: element.history ? System.decryptDataWithUserKey(element.history, userKey) : '', + politics: element.politics ? System.decryptDataWithUserKey(element.politics, userKey) : '', + economy: element.economy ? System.decryptDataWithUserKey(element.economy, userKey) : '', + religion: element.religion ? System.decryptDataWithUserKey(element.religion, userKey) : '', + languages: element.languages ? System.decryptDataWithUserKey(element.languages, userKey) : '', + laws: [], + biomes: [], + issues: [], + customs: [], + kingdoms: [], + climate: [], + resources: [], + wildlife: [], + arts: [], + ethnicGroups: [], + socialClasses: [], + importantCharacters: [], + }; + + worlds.push(newWorld); + + if (element.element_type) { + const newElement = { + id: element.element_id as string, + name: element.element_name ? System.decryptDataWithUserKey(element.element_name, userKey) : '', + description: element.element_description ? System.decryptDataWithUserKey(element.element_description, userKey) : '' + }; + + switch (element.element_type) { + case 1: + worlds[worlds.length - 1].laws.push(newElement); + break; + case 2: + worlds[worlds.length - 1].biomes.push(newElement); + break; + case 3: + worlds[worlds.length - 1].issues.push(newElement); + break; + case 4: + worlds[worlds.length - 1].customs.push(newElement); + break; + case 5: + worlds[worlds.length - 1].kingdoms.push(newElement); + break; + case 6: + worlds[worlds.length - 1].climate.push(newElement); + break; + case 7: + worlds[worlds.length - 1].resources.push(newElement); + break; + case 8: + worlds[worlds.length - 1].wildlife.push(newElement); + break; + case 9: + worlds[worlds.length - 1].arts.push(newElement); + break; + case 10: + worlds[worlds.length - 1].ethnicGroups.push(newElement); + break; + case 11: + worlds[worlds.length - 1].socialClasses.push(newElement); + break; + case 12: + worlds[worlds.length - 1].importantCharacters.push(newElement); + break; + } + } + } else { + const existingElement = { + id: element.element_id as string, + name: element.element_name ? System.decryptDataWithUserKey(element.element_name, userKey) : '', + description: element.element_description ? System.decryptDataWithUserKey(element.element_description, userKey) : '' + }; + + switch (element.element_type) { + case 1: + existWorld.laws.push(existingElement); + break; + case 2: + existWorld.biomes.push(existingElement); + break; + case 3: + existWorld.issues.push(existingElement); + break; + case 4: + existWorld.customs.push(existingElement); + break; + case 5: + existWorld.kingdoms.push(existingElement); + break; + case 6: + existWorld.climate.push(existingElement); + break; + case 7: + existWorld.resources.push(existingElement); + break; + case 8: + existWorld.wildlife.push(existingElement); + break; + case 9: + existWorld.arts.push(existingElement); + break; + case 10: + existWorld.ethnicGroups.push(existingElement); + break; + case 11: + existWorld.socialClasses.push(existingElement); + break; + case 12: + existWorld.importantCharacters.push(existingElement); + break; + } + } + } + return worlds; + } + + public static updateWorld(userId: string, world: WorldProps, lang: 'fr' | 'en' = 'fr'): boolean { + const userKey: string = getUserEncryptionKey(userId); + const encryptName: string = world.name ? System.encryptDataWithUserKey(world.name, userKey) : ''; + const encryptHistory: string = world.history ? System.encryptDataWithUserKey(world.history, userKey) : ''; + const encryptPolitics: string = world.politics ? System.encryptDataWithUserKey(world.politics, userKey) : ''; + const encryptEconomy: string = world.economy ? System.encryptDataWithUserKey(world.economy, userKey) : ''; + const encryptReligion: string = world.religion ? System.encryptDataWithUserKey(world.religion, userKey) : ''; + const encryptLanguages: string = world.languages ? System.encryptDataWithUserKey(world.languages, userKey) : ''; + let elements: WorldElementValue[] = []; + const elementTypes: { key: keyof WorldProps, elements: WorldElement[] }[] = [ + {key: 'laws', elements: world.laws}, + {key: 'biomes', elements: world.biomes}, + {key: 'issues', elements: world.issues}, + {key: 'customs', elements: world.customs}, + {key: 'kingdoms', elements: world.kingdoms}, + {key: 'climate', elements: world.climate}, + {key: 'resources', elements: world.resources}, + {key: 'wildlife', elements: world.wildlife}, + {key: 'arts', elements: world.arts}, + {key: 'ethnicGroups', elements: world.ethnicGroups}, + {key: 'socialClasses', elements: world.socialClasses}, + {key: 'importantCharacters', elements: world.importantCharacters} + ]; + + elementTypes.forEach(({key, elements: elementsList}) => { + elements = elements.concat(elementsList.map((element: WorldElement) => { + const encryptedName: string = System.encryptDataWithUserKey(element.name, userKey); + const hashedName: string = System.hashElement(element.name); + const encryptedDescription: string = element.description ? System.encryptDataWithUserKey(element.description, userKey) : ''; + const elementType: number = Book.getElementTypes(key); + + return { + id: element.id, + name: encryptedName, + hashedName: hashedName, + description: encryptedDescription, + type: elementType + }; + })); + }); + + BookRepo.updateWorld(userId, world.id, encryptName, System.hashElement(world.name), encryptHistory, encryptPolitics, encryptEconomy, encryptReligion, encryptLanguages, lang); + return BookRepo.updateWorldElements(userId, elements, lang); + } + + public static addNewElementToWorld(userId: string, worldId: string, elementName: string, elementType: string, lang: 'fr' | 'en' = 'fr'): string { + const userKey: string = getUserEncryptionKey(userId); + const hashedName: string = System.hashElement(elementName); + if (BookRepo.checkElementExist(worldId, hashedName, lang)) { + throw new Error(lang === "fr" ? `Vous avez déjà un élément avec ce nom ${elementName}.` : `You already have an element named ${elementName}.`); + } + const elementTypeId: number = Book.getElementTypes(elementType); + const encryptedName: string = System.encryptDataWithUserKey(elementName, userKey); + const elementId: string = System.createUniqueId(); + return BookRepo.insertNewElement(userId, elementId, elementTypeId, worldId, encryptedName, hashedName, lang); + } + public static getElementTypes(elementType:string):number{ + switch (elementType){ + case 'laws': + return 1; + case 'biomes': + return 2; + case 'issues': + return 3; + case 'customs': + return 4; + case 'kingdoms': + return 5; + case 'climate': + return 6; + case 'resources': + return 7; + case 'wildlife': + return 8; + case 'arts': + return 9; + case 'ethnicGroups': + return 10; + case 'socialClasses': + return 11; + case 'importantCharacters': + return 12; + default: + return 0; + } + } + + public static removeElementFromWorld(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean { + return BookRepo.deleteElement(userId, elementId, lang); + } + + public static removeBook(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean { + return BookRepo.deleteBook(userId, bookId, lang); + } + + static getGuideLineAI(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): GuideLineAI { + const userKey: string = getUserEncryptionKey(userId); + try { + const guideLine: GuideLineAIQuery = BookRepo.fetchGuideLineAI(userId, bookId, lang); + return { + narrativeType: guideLine.narrative_type, + dialogueType: guideLine.dialogue_type, + globalResume: guideLine.global_resume ? System.decryptDataWithUserKey(guideLine.global_resume, userKey) : '', + atmosphere: guideLine.atmosphere ? System.decryptDataWithUserKey(guideLine.atmosphere, userKey) : '', + verbeTense: guideLine.verbe_tense, + themes: guideLine.themes ? System.decryptDataWithUserKey(guideLine.themes, userKey) : '', + currentResume: guideLine.current_resume ? System.decryptDataWithUserKey(guideLine.current_resume, userKey) : '', + langue: guideLine.langue + } + } catch (e: unknown) { + if (e instanceof Error && e.message.includes('not found')) { + return { + narrativeType: 0, + dialogueType: 0, + globalResume: '', + atmosphere: '', + verbeTense: 0, + themes: '', + currentResume: '', + langue: 0 + } + } + if (e instanceof Error) { + throw new Error(e.message); + } else { + console.error(lang === 'fr' ? "Erreur inconnue lors de la récupération de la ligne directrice de l'IA." : "Unknown error while fetching AI guideline."); + throw new Error(lang === 'fr' ? "Erreur inconnue lors de la récupération de la ligne directrice de l'IA." : "Unknown error while fetching AI guideline."); + } + } + } + + public static setAIGuideLine(userId: string, bookId: string, narrativeType: number, dialogueType: number, plotSummary: string, toneAtmosphere: string, verbTense: number, language: number, themes: string, lang: 'fr' | 'en' = 'fr'): boolean { + const userKey: string = getUserEncryptionKey(userId); + const encryptedPlotSummary: string = plotSummary ? System.encryptDataWithUserKey(plotSummary, userKey) : ''; + const encryptedToneAtmosphere: string = toneAtmosphere ? System.encryptDataWithUserKey(toneAtmosphere, userKey) : ''; + const encryptedThemes: string = themes ? System.encryptDataWithUserKey(themes, userKey) : ''; + return BookRepo.insertAIGuideLine(userId, bookId, narrativeType, dialogueType, encryptedPlotSummary, encryptedToneAtmosphere, verbTense, language, encryptedThemes, lang); + } + + public getId(): string { + return this.id; + } + + public getAuthorId(): string { + return this.authorId; + } + + public getTitle(): string { + return this.title; + } + + public getSubTitle(): string { + return this.subTitle; + } + + public getSummary(): string { + return this.summary; + } + + public getSerieId(): number { + return this.serieId; + } + + public getDesiredReleaseDate(): string { + return this.desiredReleaseDate; + } + + public getDesiredWordCount(): number { + return this.desiredWordCount; + } + + public getWordCount(): number { + return this.wordCount; + } + + public getCover(): string { + return this.cover; + } + + public getType(): string { + return this.type; + } + + static completeBookData(userId: string, id: string, lang: 'fr' | 'en' = 'fr'): CompleteBookData { + const data = BookRepo.fetchBook(id, userId, lang); + const chapters: ChapterBookResult[] = BookRepo.fetchCompleteBookChapters(id, lang); + const userKey: string = getUserEncryptionKey(userId); + const userInfos = UserRepo.fetchAccountInformation(userId, lang); + + const bookTitle: string = data.title ? System.decryptDataWithUserKey(data.title, userKey) : ''; + const decryptedChapters: any[] = []; + + for (const chapter of chapters) { + if (!chapter.meta_chapter) continue; + if (!chapter.meta_chapter_content) continue; + 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 = data.cover_image ? Book.getPicture(userId, userKey, data.cover_image, lang) : ''; + return { + bookId: id, + title: bookTitle, + subTitle: data.sub_title ? System.decryptDataWithUserKey(data.sub_title, userKey) : '', + summary: data.summary ? System.decryptDataWithUserKey(data.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 + }; + } + + static getChaptersOrSheet(bookChapters: CompleteChapterContent[]): ChapterContentData[] { + const chapters: ChapterContentData[] = []; + const haveSheet: CompleteChapterContent | undefined = bookChapters.find((chapter: CompleteChapterContent): boolean => chapter.order === -1); + const haveChapter: CompleteChapterContent | undefined = bookChapters.find((chapter: CompleteChapterContent): boolean => chapter.order > 0); + if (haveSheet && !haveChapter) { + chapters.push({ + title: haveSheet.title, + chapterOrder: haveSheet.order, + content: System.htmlToText(Chapter.tipTapToHtml(JSON.parse(haveSheet.content))), + wordsCount: 0, + version: haveSheet.version || 0 + }); + } else if (haveChapter) { + for (const chapter of bookChapters) { + if (chapter.order < 0) continue; + chapters.push({ + title: chapter.title, + chapterOrder: chapter.order, + content: System.htmlToText(Chapter.tipTapToHtml(JSON.parse(chapter.content))), + wordsCount: 0, + version: chapter.version || 0 + }); + } + } + return chapters; + } + + static getAllChapters(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterContentData[] { + try { + const book: CompleteBookData = Book.completeBookData(userId, bookId, lang); + return Book.getChaptersOrSheet(book.chapters); + } catch (e: unknown) { + return []; + } + } + + public getBookInfos(userId: string, lang: 'fr' | 'en' = 'fr'): void { + const book: BookQuery = BookRepo.fetchBook(this.id, userId, lang); + const userKey: string = getUserEncryptionKey(userId); + if (book) { + this.authorId = book.author_id; + this.type = book.type; + this.title = book.title ? System.decryptDataWithUserKey(book.title, userKey) : ''; + this.subTitle = book.sub_title ? System.decryptDataWithUserKey(book.sub_title, userKey) : ''; + this.summary = book.summary ? System.decryptDataWithUserKey(book.summary, userKey) : ''; + this.serieId = book.serie_id ?? 0; + this.desiredReleaseDate = book.desired_release_date ?? ''; + this.desiredWordCount = book.desired_word_count ?? 0; + this.wordCount = book.words_count ?? 0; + this.cover = book.cover_image ? Book.getPicture(userId, userKey, book.cover_image, lang) : ''; + } else { + this.authorId = ''; + this.title = ''; + this.subTitle = ''; + this.summary = ''; + this.serieId = 0; + this.desiredReleaseDate = ''; + this.wordCount = 0; + this.cover = ''; + } + } +} \ No newline at end of file