import type { ActQuery, BookActSummariesTable, BookAIGuideLineTable, BookChapterContentTable, BookChapterInfosTable, BookChaptersTable, BookCharactersAttributesTable, BookCharactersTable, BookGuideLineTable, BookIncidentsTable, BookIssuesTable, BookLocationTable, BookPlotPointsTable, BookWorldElementsTable, BookWorldTable, EritBooksTable, IncidentQuery, IssueQuery, LocationElementTable, LocationSubElementTable, PlotPointQuery, SyncedActSummaryResult, SyncedAIGuideLineResult, SyncedBookResult, SyncedChapterContentResult, SyncedChapterInfoResult, SyncedChapterResult, SyncedCharacterAttributeResult, SyncedCharacterResult, SyncedGuideLineResult, SyncedIncidentResult, SyncedIssueResult, SyncedLocationElementResult, SyncedLocationResult, SyncedLocationSubElementResult, SyncedPlotPointResult, SyncedWorldElementResult, SyncedWorldResult, WorldQuery } from '../repositories/book.repository.js'; import BookRepository from '../repositories/book.repository.js'; import BookRepo, { BookCoverQuery, BookQuery, ChapterBookResult, GuideLineAIQuery, GuideLineQuery, WorldElementValue } from '../repositories/book.repository.js'; import System from '../System.js'; import {getUserEncryptionKey} from '../keyManager.js'; import path from "path"; import fs from "fs"; import Chapter, {ActChapter, ChapterContentData, ChapterProps} from "./Chapter.js"; import UserRepo from "../repositories/user.repository.js"; import ChapterRepo from "../repositories/chapter.repository.js"; import CharacterRepo from "../repositories/character.repository.js"; import LocationRepo from "../repositories/location.repository.js"; 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 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[]; } 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; } 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; } export interface SyncedChapter { id: string; name: string; lastUpdate: number; contents: SyncedChapterContent[]; info: SyncedChapterInfo | null; } export interface SyncedChapterContent { id: string; lastUpdate: number; } export interface SyncedChapterInfo { id: string; lastUpdate: number; } export interface SyncedCharacter { id: string; name: string; lastUpdate: number; attributes: SyncedCharacterAttribute[]; } export interface SyncedCharacterAttribute { id: string; name: string; lastUpdate: number; } export interface SyncedLocation { id: string; name: string; lastUpdate: number; elements: SyncedLocationElement[]; } export interface SyncedLocationElement { id: string; name: string; lastUpdate: number; subElements: SyncedLocationSubElement[]; } export interface SyncedLocationSubElement { id: string; name: string; lastUpdate: number; } export interface SyncedWorld { id: string; name: string; lastUpdate: number; elements: SyncedWorldElement[]; } export interface SyncedWorldElement { id: string; name: string; lastUpdate: number; } export interface SyncedIncident { id: string; name: string; lastUpdate: number; } export interface SyncedPlotPoint { id: string; name: string; lastUpdate: number; } export interface SyncedIssue { id: string; name: string; lastUpdate: number; } export interface SyncedActSummary { id: string; lastUpdate: number; } export interface SyncedGuideLine { lastUpdate: number; } export interface SyncedAIGuideLine { lastUpdate: number; } 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'): 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,System.timeStampInSeconds(),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, System.timeStampInSeconds(), 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, System.timeStampInSeconds(), 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, System.timeStampInSeconds(), 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, System.timeStampInSeconds(), 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) { 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 = ''; } } static async getSyncedBooks(userId: string, lang: 'fr' | 'en'):Promise { const userKey: string = getUserEncryptionKey(userId); const [ allBooks, allChapters, allChapterContents, allChapterInfos, allCharacters, allCharacterAttributes, allLocations, allLocationElements, allLocationSubElements, allWorlds, allWorldElements, allIncidents, allPlotPoints, allIssues, allActSummaries, allGuidelines, allAIGuidelines ]: [ SyncedBookResult[], SyncedChapterResult[], SyncedChapterContentResult[], SyncedChapterInfoResult[], SyncedCharacterResult[], SyncedCharacterAttributeResult[], SyncedLocationResult[], SyncedLocationElementResult[], SyncedLocationSubElementResult[], SyncedWorldResult[], SyncedWorldElementResult[], SyncedIncidentResult[], SyncedPlotPointResult[], SyncedIssueResult[], SyncedActSummaryResult[], SyncedGuideLineResult[], SyncedAIGuideLineResult[] ] = await Promise.all([ BookRepo.fetchSyncedBooks(userId,lang), BookRepo.fetchSyncedChapters(userId,lang), BookRepo.fetchSyncedChapterContents(userId,lang), BookRepo.fetchSyncedChapterInfos(userId,lang), BookRepo.fetchSyncedCharacters(userId,lang), BookRepo.fetchSyncedCharacterAttributes(userId,lang), BookRepo.fetchSyncedLocations(userId,lang), BookRepo.fetchSyncedLocationElements(userId,lang), BookRepo.fetchSyncedLocationSubElements(userId,lang), BookRepo.fetchSyncedWorlds(userId,lang), BookRepo.fetchSyncedWorldElements(userId,lang), BookRepo.fetchSyncedIncidents(userId,lang), BookRepo.fetchSyncedPlotPoints(userId,lang), BookRepo.fetchSyncedIssues(userId,lang), BookRepo.fetchSyncedActSummaries(userId,lang), BookRepo.fetchSyncedGuideLine(userId,lang), BookRepo.fetchSyncedAIGuideLine(userId,lang) ]); return allBooks.map((book: SyncedBookResult): SyncedBook => { const bookId: string = book.book_id; const chapters: SyncedChapter[] = allChapters .filter((chapter: SyncedChapterResult): boolean => chapter.book_id === bookId) .map((chapter: SyncedChapterResult): SyncedChapter => { const chapterId: string = chapter.chapter_id; const contents: SyncedChapterContent[] = allChapterContents .filter((content: SyncedChapterContentResult): boolean => content.chapter_id === chapterId) .map((content: SyncedChapterContentResult): SyncedChapterContent => ({ id: content.content_id, lastUpdate: content.last_update })); const infoData: SyncedChapterInfoResult | undefined = allChapterInfos.find((info: SyncedChapterInfoResult): boolean => info.chapter_id === chapterId); const info: SyncedChapterInfo | null = infoData ? { id: infoData.chapter_info_id, lastUpdate: infoData.last_update } : null; return { id: chapterId, name: System.decryptDataWithUserKey(chapter.title, userKey), lastUpdate: chapter.last_update, contents, info }; }); const characters: SyncedCharacter[] = allCharacters .filter((character: SyncedCharacterResult): boolean => character.book_id === bookId) .map((character: SyncedCharacterResult): SyncedCharacter => { const characterId: string = character.character_id; const attributes: SyncedCharacterAttribute[] = allCharacterAttributes .filter((attribute: SyncedCharacterAttributeResult): boolean => attribute.character_id === characterId) .map((attribute: SyncedCharacterAttributeResult): SyncedCharacterAttribute => ({ id: attribute.attr_id, name: System.decryptDataWithUserKey(attribute.attribute_name, userKey), lastUpdate: attribute.last_update })); return { id: characterId, name: System.decryptDataWithUserKey(character.first_name, userKey), lastUpdate: character.last_update, attributes }; }); const locations: SyncedLocation[] = allLocations .filter((location: SyncedLocationResult): boolean => location.book_id === bookId) .map((location: SyncedLocationResult): SyncedLocation => { const locationId: string = location.loc_id; const elements: SyncedLocationElement[] = allLocationElements .filter((element: SyncedLocationElementResult): boolean => element.location === locationId) .map((element: SyncedLocationElementResult): SyncedLocationElement => { const elementId: string = element.element_id; const subElements: SyncedLocationSubElement[] = allLocationSubElements .filter((subElement: SyncedLocationSubElementResult): boolean => subElement.element_id === elementId) .map((subElement: SyncedLocationSubElementResult): SyncedLocationSubElement => ({ id: subElement.sub_element_id, name: System.decryptDataWithUserKey(subElement.sub_elem_name, userKey), lastUpdate: subElement.last_update })); return { id: elementId, name: System.decryptDataWithUserKey(element.element_name, userKey), lastUpdate: element.last_update, subElements }; }); return { id: locationId, name: System.decryptDataWithUserKey(location.loc_name, userKey), lastUpdate: location.last_update, elements }; }); const worlds: SyncedWorld[] = allWorlds .filter((world: SyncedWorldResult): boolean => world.book_id === bookId) .map((world: SyncedWorldResult): SyncedWorld => { const worldId: string = world.world_id; const elements: SyncedWorldElement[] = allWorldElements .filter((worldElement: SyncedWorldElementResult): boolean => worldElement.world_id === worldId) .map((worldElement: SyncedWorldElementResult): SyncedWorldElement => ({ id: worldElement.element_id, name: System.decryptDataWithUserKey(worldElement.name, userKey), lastUpdate: worldElement.last_update })); return { id: worldId, name: System.decryptDataWithUserKey(world.name, userKey), lastUpdate: world.last_update, elements }; }); const incidents: SyncedIncident[] = allIncidents .filter((incident: SyncedIncidentResult): boolean => incident.book_id === bookId) .map((incident: SyncedIncidentResult): SyncedIncident => ({ id: incident.incident_id, name: System.decryptDataWithUserKey(incident.title, userKey), lastUpdate: incident.last_update })); const plotPoints: SyncedPlotPoint[] = allPlotPoints .filter((plotPoint: SyncedPlotPointResult): boolean => plotPoint.book_id === bookId) .map((plotPoint: SyncedPlotPointResult): SyncedPlotPoint => ({ id: plotPoint.plot_point_id, name: System.decryptDataWithUserKey(plotPoint.title, userKey), lastUpdate: plotPoint.last_update })); const issues: SyncedIssue[] = allIssues .filter((issue: SyncedIssueResult): boolean => issue.book_id === bookId) .map((issue: SyncedIssueResult): SyncedIssue => ({ id: issue.issue_id, name: System.decryptDataWithUserKey(issue.name, userKey), lastUpdate: issue.last_update })); const actSummaries: SyncedActSummary[] = allActSummaries .filter((actSummary: SyncedActSummaryResult): boolean => actSummary.book_id === bookId) .map((actSummary: SyncedActSummaryResult): SyncedActSummary => ({ id: actSummary.act_sum_id, lastUpdate: actSummary.last_update })); const guidelineData: SyncedGuideLineResult | undefined = allGuidelines.find((guideline: SyncedGuideLineResult): boolean => guideline.book_id === bookId); const guideLine: SyncedGuideLine | null = guidelineData ? { lastUpdate: guidelineData.last_update } : null; const aiGuidelineData: SyncedAIGuideLineResult | undefined = allAIGuidelines.find((aiGuideline: SyncedAIGuideLineResult): boolean => aiGuideline.book_id === bookId); const aiGuideLine: SyncedAIGuideLine | null = aiGuidelineData ? { lastUpdate: aiGuidelineData.last_update } : null; return { id: bookId, type: book.type, title: System.decryptDataWithUserKey(book.title, userKey), subTitle: book.sub_title ? System.decryptDataWithUserKey(book.sub_title, userKey) : null, lastUpdate: book.last_update, chapters, characters, locations, worlds, incidents, plotPoints, issues, actSummaries, guideLine, aiGuideLine }; }); } static async uploadBookForSync(userId:string,bookId: string,lang: "fr" | "en"): Promise { const userKey: string = getUserEncryptionKey(userId); const [ eritBooksRaw, actSummariesRaw, aiGuideLineRaw, chaptersRaw, charactersRaw, guideLineRaw, incidentsRaw, issuesRaw, locationsRaw, plotPointsRaw, worldsRaw ]: [ EritBooksTable[], BookActSummariesTable[], BookAIGuideLineTable[], BookChaptersTable[], BookCharactersTable[], BookGuideLineTable[], BookIncidentsTable[], BookIssuesTable[], BookLocationTable[], BookPlotPointsTable[], BookWorldTable[] ] = await Promise.all([ BookRepo.fetchEritBooksTable(userId, bookId,lang), BookRepo.fetchBookActSummaries(userId, bookId,lang), BookRepo.fetchBookAIGuideLine(userId, bookId,lang), BookRepo.fetchBookChapters(userId, bookId,lang), BookRepo.fetchBookCharacters(userId, bookId,lang), BookRepo.fetchBookGuideLineTable(userId, bookId,lang), BookRepo.fetchBookIncidents(userId, bookId,lang), BookRepo.fetchBookIssues(userId, bookId,lang), BookRepo.fetchBookLocations(userId, bookId,lang), BookRepo.fetchBookPlotPoints(userId, bookId,lang), BookRepo.fetchBookWorlds(userId, bookId,lang) ]); const [ chapterContentsNested, chapterInfosNested, characterAttributesNested, worldElementsNested, locationElementsNested ]: [ BookChapterContentTable[][], BookChapterInfosTable[][], BookCharactersAttributesTable[][], BookWorldElementsTable[][], LocationElementTable[][] ] = await Promise.all([ Promise.all(chaptersRaw.map((chapter: BookChaptersTable): Promise => BookRepo.fetchBookChapterContents(userId, chapter.chapter_id,lang))), Promise.all(chaptersRaw.map((chapter: BookChaptersTable): Promise => BookRepo.fetchBookChapterInfos(userId, chapter.chapter_id,lang))), Promise.all(charactersRaw.map((character: BookCharactersTable): Promise => BookRepo.fetchBookCharactersAttributes(userId, character.character_id,lang))), Promise.all(worldsRaw.map((world: BookWorldTable): Promise => BookRepo.fetchBookWorldElements(userId, world.world_id,lang))), Promise.all(locationsRaw.map((location: BookLocationTable): Promise => BookRepo.fetchLocationElements(userId, location.loc_id,lang))) ]); const chapterContentsRaw: BookChapterContentTable[] = chapterContentsNested.flat(); const chapterInfosRaw: BookChapterInfosTable[] = chapterInfosNested.flat(); const characterAttributesRaw: BookCharactersAttributesTable[] = characterAttributesNested.flat(); const worldElementsRaw: BookWorldElementsTable[] = worldElementsNested.flat(); const locationElementsRaw: LocationElementTable[] = locationElementsNested.flat(); const locationSubElementsNested: LocationSubElementTable[][] = await Promise.all( locationElementsRaw.map((element: LocationElementTable): Promise => BookRepo.fetchLocationSubElements(userId, element.element_id,lang)) ); const locationSubElementsRaw: LocationSubElementTable[] = locationSubElementsNested.flat(); const eritBooks: EritBooksTable[] = eritBooksRaw.map((book: EritBooksTable): EritBooksTable => ({ ...book, title: System.decryptDataWithUserKey(book.title, userKey), sub_title: book.sub_title ? System.decryptDataWithUserKey(book.sub_title, userKey) : null, summary: book.summary ? System.decryptDataWithUserKey(book.summary, userKey) : null, cover_image: book.cover_image ? System.decryptDataWithUserKey(book.cover_image, userKey) : null })); const actSummaries: BookActSummariesTable[] = actSummariesRaw.map((actSummary: BookActSummariesTable): BookActSummariesTable => ({ ...actSummary, summary: actSummary.summary ? System.decryptDataWithUserKey(actSummary.summary, userKey) : null })); const aiGuideLine: BookAIGuideLineTable[] = aiGuideLineRaw.map((guideLine: BookAIGuideLineTable): BookAIGuideLineTable => ({ ...guideLine, global_resume: guideLine.global_resume ? System.decryptDataWithUserKey(guideLine.global_resume, userKey) : null, themes: guideLine.themes ? System.decryptDataWithUserKey(guideLine.themes, userKey) : null, tone: guideLine.tone ? System.decryptDataWithUserKey(guideLine.tone, userKey) : null, atmosphere: guideLine.atmosphere ? System.decryptDataWithUserKey(guideLine.atmosphere, userKey) : null, current_resume: guideLine.current_resume ? System.decryptDataWithUserKey(guideLine.current_resume, userKey) : null })); const chapters: BookChaptersTable[] = chaptersRaw.map((chapter: BookChaptersTable): BookChaptersTable => ({ ...chapter, title: System.decryptDataWithUserKey(chapter.title, userKey) })); const chapterContents: BookChapterContentTable[] = chapterContentsRaw.map((chapterContent: BookChapterContentTable): BookChapterContentTable => ({ ...chapterContent, content: chapterContent.content ? JSON.parse(System.decryptDataWithUserKey(chapterContent.content, userKey)) : null })); const chapterInfos: BookChapterInfosTable[] = chapterInfosRaw.map((chapterInfo: BookChapterInfosTable): BookChapterInfosTable => ({ ...chapterInfo, summary: chapterInfo.summary ? System.decryptDataWithUserKey(chapterInfo.summary, userKey) : null, goal: chapterInfo.goal ? System.decryptDataWithUserKey(chapterInfo.goal, userKey) : null })); const characters: BookCharactersTable[] = charactersRaw.map((character: BookCharactersTable): BookCharactersTable => ({ ...character, first_name: System.decryptDataWithUserKey(character.first_name, userKey), last_name: character.last_name ? System.decryptDataWithUserKey(character.last_name, userKey) : null, category: System.decryptDataWithUserKey(character.category, userKey), title: character.title ? System.decryptDataWithUserKey(character.title, userKey) : null, role: character.role ? System.decryptDataWithUserKey(character.role, userKey) : null, biography: character.biography ? System.decryptDataWithUserKey(character.biography, userKey) : null, history: character.history ? System.decryptDataWithUserKey(character.history, userKey) : null })); const characterAttributes: BookCharactersAttributesTable[] = characterAttributesRaw.map((attribute: BookCharactersAttributesTable): BookCharactersAttributesTable => ({ ...attribute, attribute_name: System.decryptDataWithUserKey(attribute.attribute_name, userKey), attribute_value: System.decryptDataWithUserKey(attribute.attribute_value, userKey) })); const guideLine: BookGuideLineTable[] = guideLineRaw.map((guide: BookGuideLineTable): BookGuideLineTable => ({ ...guide, tone: guide.tone ? System.decryptDataWithUserKey(guide.tone, userKey) : null, atmosphere: guide.atmosphere ? System.decryptDataWithUserKey(guide.atmosphere, userKey) : null, writing_style: guide.writing_style ? System.decryptDataWithUserKey(guide.writing_style, userKey) : null, themes: guide.themes ? System.decryptDataWithUserKey(guide.themes, userKey) : null, symbolism: guide.symbolism ? System.decryptDataWithUserKey(guide.symbolism, userKey) : null, motifs: guide.motifs ? System.decryptDataWithUserKey(guide.motifs, userKey) : null, narrative_voice: guide.narrative_voice ? System.decryptDataWithUserKey(guide.narrative_voice, userKey) : null, pacing: guide.pacing ? System.decryptDataWithUserKey(guide.pacing, userKey) : null, intended_audience: guide.intended_audience ? System.decryptDataWithUserKey(guide.intended_audience, userKey) : null, key_messages: guide.key_messages ? System.decryptDataWithUserKey(guide.key_messages, userKey) : null })); const incidents: BookIncidentsTable[] = incidentsRaw.map((incident: BookIncidentsTable): BookIncidentsTable => ({ ...incident, title: System.decryptDataWithUserKey(incident.title, userKey), summary: incident.summary ? System.decryptDataWithUserKey(incident.summary, userKey) : null })); const issues: BookIssuesTable[] = issuesRaw.map((issue: BookIssuesTable): BookIssuesTable => ({ ...issue, name: System.decryptDataWithUserKey(issue.name, userKey) })); const locations: BookLocationTable[] = locationsRaw.map((location: BookLocationTable): BookLocationTable => ({ ...location, loc_name: System.decryptDataWithUserKey(location.loc_name, userKey) })); const plotPoints: BookPlotPointsTable[] = plotPointsRaw.map((plotPoint: BookPlotPointsTable): BookPlotPointsTable => ({ ...plotPoint, title: System.decryptDataWithUserKey(plotPoint.title, userKey), summary: plotPoint.summary ? System.decryptDataWithUserKey(plotPoint.summary, userKey) : null })); const worlds: BookWorldTable[] = worldsRaw.map((world: BookWorldTable): BookWorldTable => ({ ...world, name: System.decryptDataWithUserKey(world.name, userKey), history: world.history ? System.decryptDataWithUserKey(world.history, userKey) : null, politics: world.politics ? System.decryptDataWithUserKey(world.politics, userKey) : null, economy: world.economy ? System.decryptDataWithUserKey(world.economy, userKey) : null, religion: world.religion ? System.decryptDataWithUserKey(world.religion, userKey) : null, languages: world.languages ? System.decryptDataWithUserKey(world.languages, userKey) : null })); const worldElements: BookWorldElementsTable[] = worldElementsRaw.map((worldElement: BookWorldElementsTable): BookWorldElementsTable => ({ ...worldElement, name: System.decryptDataWithUserKey(worldElement.name, userKey), description: worldElement.description ? System.decryptDataWithUserKey(worldElement.description, userKey) : null })); const locationElements: LocationElementTable[] = locationElementsRaw.map((locationElement: LocationElementTable): LocationElementTable => ({ ...locationElement, element_name: System.decryptDataWithUserKey(locationElement.element_name, userKey), element_description: locationElement.element_description ? System.decryptDataWithUserKey(locationElement.element_description, userKey) : null })); const locationSubElements: LocationSubElementTable[] = locationSubElementsRaw.map((locationSubElement: LocationSubElementTable): LocationSubElementTable => ({ ...locationSubElement, sub_elem_name: System.decryptDataWithUserKey(locationSubElement.sub_elem_name, userKey), sub_elem_description: locationSubElement.sub_elem_description ? System.decryptDataWithUserKey(locationSubElement.sub_elem_description, userKey) : null })); return { eritBooks, actSummaries, aiGuideLine, chapters, chapterContents, chapterInfos, characters, characterAttributes, guideLine, incidents, issues, locations, plotPoints, worlds, worldElements, locationElements, locationSubElements }; } static async saveCompleteBook(userId: string, data: CompleteBook, lang: "fr" | "en"):Promise { const userKey: string = getUserEncryptionKey(userId); const book: EritBooksTable = data.eritBooks[0]; const encryptedBookTitle: string = System.encryptDataWithUserKey(book.title, userKey); const encryptedBookSubTitle: string | null = book.sub_title ? System.encryptDataWithUserKey(book.sub_title, userKey) : null; const encryptedBookSummary: string | null = book.summary ? System.encryptDataWithUserKey(book.summary, userKey) : null; const encryptedBookCoverImage: string | null = book.cover_image ? System.encryptDataWithUserKey(book.cover_image, userKey) : null; const bookInserted: boolean = BookRepo.insertSyncBook( book.book_id, userId, book.type, encryptedBookTitle, book.hashed_title, encryptedBookSubTitle, book.hashed_sub_title, encryptedBookSummary, book.serie_id, book.desired_release_date, book.desired_word_count, book.words_count, encryptedBookCoverImage, book.last_update, lang ); if (!bookInserted) return false; const chaptersInserted: boolean = data.chapters.every((chapter: BookChaptersTable): boolean => { const encryptedTitle: string = System.encryptDataWithUserKey(chapter.title, userKey); return BookRepo.insertSyncChapter(chapter.chapter_id, chapter.book_id, userId, encryptedTitle, chapter.hashed_title, chapter.words_count, chapter.chapter_order, chapter.last_update, lang); }); if (!chaptersInserted) return false; const incidentsInserted: boolean = data.incidents.every((incident: BookIncidentsTable): boolean => { const encryptedIncidentTitle: string = System.encryptDataWithUserKey(incident.title, userKey); const encryptedIncidentSummary: string | null = incident.summary ? System.encryptDataWithUserKey(incident.summary, userKey) : null; return BookRepo.insertSyncIncident(incident.incident_id, userId, incident.book_id, encryptedIncidentTitle, incident.hashed_title, encryptedIncidentSummary, incident.last_update, lang); }); if (!incidentsInserted) return false; const plotPointsInserted: boolean = data.plotPoints.every((plotPoint: BookPlotPointsTable): boolean => { const encryptedPlotTitle: string = System.encryptDataWithUserKey(plotPoint.title, userKey); const encryptedPlotSummary: string | null = plotPoint.summary ? System.encryptDataWithUserKey(plotPoint.summary, userKey) : null; return BookRepo.insertSyncPlotPoint(plotPoint.plot_point_id, encryptedPlotTitle, plotPoint.hashed_title, encryptedPlotSummary, plotPoint.linked_incident_id, userId, plotPoint.book_id, plotPoint.last_update, lang); }); if (!plotPointsInserted) return false; const chapterContentsInserted: boolean = data.chapterContents.every((content: BookChapterContentTable): boolean => { const encryptedContent: string | null = content.content ? System.encryptDataWithUserKey(JSON.stringify(content.content), userKey) : null; return BookRepo.insertSyncChapterContent(content.content_id, content.chapter_id, userId, content.version, encryptedContent, content.words_count, content.time_on_it, content.last_update, lang); }); if (!chapterContentsInserted) return false; const chapterInfosInserted: boolean = data.chapterInfos.every((info: BookChapterInfosTable): boolean => { const encryptedSummary: string | null = info.summary ? System.encryptDataWithUserKey(info.summary, userKey) : null; const encryptedGoal: string | null = info.goal ? System.encryptDataWithUserKey(info.goal, userKey) : null; return BookRepo.insertSyncChapterInfo(info.chapter_info_id, info.chapter_id, info.act_id, info.incident_id, info.plot_point_id, info.book_id, userId, encryptedSummary, encryptedGoal, info.last_update, lang); }); if (!chapterInfosInserted) return false; const charactersInserted: boolean = data.characters.every((character: BookCharactersTable): boolean => { const encryptedFirstName: string = System.encryptDataWithUserKey(character.first_name, userKey); const encryptedLastName: string | null = character.last_name ? System.encryptDataWithUserKey(character.last_name, userKey) : null; const encryptedCategory: string = System.encryptDataWithUserKey(character.category, userKey); const encryptedCharTitle: string | null = character.title ? System.encryptDataWithUserKey(character.title, userKey) : null; const encryptedRole: string | null = character.role ? System.encryptDataWithUserKey(character.role, userKey) : null; const encryptedBiography: string | null = character.biography ? System.encryptDataWithUserKey(character.biography, userKey) : null; const encryptedCharHistory: string | null = character.history ? System.encryptDataWithUserKey(character.history, userKey) : null; return BookRepo.insertSyncCharacter(character.character_id, character.book_id, userId, encryptedFirstName, encryptedLastName, encryptedCategory, encryptedCharTitle, character.image, encryptedRole, encryptedBiography, encryptedCharHistory, character.last_update, lang); }); if (!charactersInserted) return false; const characterAttributesInserted: boolean = data.characterAttributes.every((attr: BookCharactersAttributesTable): boolean => { const encryptedAttrName: string = System.encryptDataWithUserKey(attr.attribute_name, userKey); const encryptedAttrValue: string = System.encryptDataWithUserKey(attr.attribute_value, userKey); return BookRepo.insertSyncCharacterAttribute(attr.attr_id, attr.character_id, userId, encryptedAttrName, encryptedAttrValue, attr.last_update, lang); }); if (!characterAttributesInserted) return false; const locationsInserted: boolean = data.locations.every((location: BookLocationTable): boolean => { const encryptedLocName: string = System.encryptDataWithUserKey(location.loc_name, userKey); return BookRepo.insertSyncLocation(location.loc_id, location.book_id, userId, encryptedLocName, location.loc_original_name, location.last_update, lang); }); if (!locationsInserted) return false; const locationElementsInserted: boolean = data.locationElements.every((element: LocationElementTable): boolean => { const encryptedLocElemName: string = System.encryptDataWithUserKey(element.element_name, userKey); const encryptedLocElemDesc: string | null = element.element_description ? System.encryptDataWithUserKey(element.element_description, userKey) : null; return BookRepo.insertSyncLocationElement(element.element_id, element.location, userId, encryptedLocElemName, element.original_name, encryptedLocElemDesc, element.last_update, lang); }); if (!locationElementsInserted) return false; const locationSubElementsInserted: boolean = data.locationSubElements.every((subElement: LocationSubElementTable): boolean => { const encryptedSubElemName: string = System.encryptDataWithUserKey(subElement.sub_elem_name, userKey); const encryptedSubElemDesc: string | null = subElement.sub_elem_description ? System.encryptDataWithUserKey(subElement.sub_elem_description, userKey) : null; return BookRepo.insertSyncLocationSubElement(subElement.sub_element_id, subElement.element_id, userId, encryptedSubElemName, subElement.original_name, encryptedSubElemDesc, subElement.last_update, lang); }); if (!locationSubElementsInserted) return false; const worldsInserted: boolean = data.worlds.every((world: BookWorldTable): boolean => { const encryptedWorldName: string = System.encryptDataWithUserKey(world.name, userKey); const encryptedWorldHistory: string | null = world.history ? System.encryptDataWithUserKey(world.history, userKey) : null; const encryptedPolitics: string | null = world.politics ? System.encryptDataWithUserKey(world.politics, userKey) : null; const encryptedEconomy: string | null = world.economy ? System.encryptDataWithUserKey(world.economy, userKey) : null; const encryptedReligion: string | null = world.religion ? System.encryptDataWithUserKey(world.religion, userKey) : null; const encryptedLanguages: string | null = world.languages ? System.encryptDataWithUserKey(world.languages, userKey) : null; return BookRepo.insertSyncWorld(world.world_id, encryptedWorldName, world.hashed_name, userId, world.book_id, encryptedWorldHistory, encryptedPolitics, encryptedEconomy, encryptedReligion, encryptedLanguages, world.last_update, lang); }); if (!worldsInserted) return false; const worldElementsInserted: boolean = data.worldElements.every((element: BookWorldElementsTable): boolean => { const encryptedElemName: string = System.encryptDataWithUserKey(element.name, userKey); const encryptedElemDesc: string | null = element.description ? System.encryptDataWithUserKey(element.description, userKey) : null; return BookRepo.insertSyncWorldElement(element.element_id, element.world_id, userId, element.element_type, encryptedElemName, element.original_name, encryptedElemDesc, element.last_update, lang); }); if (!worldElementsInserted) return false; const actSummariesInserted: boolean = data.actSummaries.every((actSummary: BookActSummariesTable): boolean => { const encryptedSummary: string | null = actSummary.summary ? System.encryptDataWithUserKey(actSummary.summary, userKey) : null; return BookRepo.insertSyncActSummary(actSummary.act_sum_id, actSummary.book_id, userId, actSummary.act_index, encryptedSummary, actSummary.last_update, lang); }); if (!actSummariesInserted) return false; const aiGuidelinesInserted: boolean = data.aiGuideLine.every((aiGuide: BookAIGuideLineTable): boolean => { const encryptedGlobalResume: string | null = aiGuide.global_resume ? System.encryptDataWithUserKey(aiGuide.global_resume, userKey) : null; const encryptedAIThemes: string | null = aiGuide.themes ? System.encryptDataWithUserKey(aiGuide.themes, userKey) : null; const encryptedAITone: string | null = aiGuide.tone ? System.encryptDataWithUserKey(aiGuide.tone, userKey) : null; const encryptedAIAtmosphere: string | null = aiGuide.atmosphere ? System.encryptDataWithUserKey(aiGuide.atmosphere, userKey) : null; const encryptedCurrentResume: string | null = aiGuide.current_resume ? System.encryptDataWithUserKey(aiGuide.current_resume, userKey) : null; return BookRepo.insertSyncAIGuideLine(userId, aiGuide.book_id, encryptedGlobalResume, encryptedAIThemes, aiGuide.verbe_tense, aiGuide.narrative_type, aiGuide.langue, aiGuide.dialogue_type, encryptedAITone, encryptedAIAtmosphere, encryptedCurrentResume, aiGuide.last_update, lang); }); if (!aiGuidelinesInserted) return false; const guidelinesInserted: boolean = data.guideLine.every((guide: BookGuideLineTable): boolean => { const encryptedGuideTone: string | null = guide.tone ? System.encryptDataWithUserKey(guide.tone, userKey) : null; const encryptedGuideAtmosphere: string | null = guide.atmosphere ? System.encryptDataWithUserKey(guide.atmosphere, userKey) : null; const encryptedWritingStyle: string | null = guide.writing_style ? System.encryptDataWithUserKey(guide.writing_style, userKey) : null; const encryptedGuideThemes: string | null = guide.themes ? System.encryptDataWithUserKey(guide.themes, userKey) : null; const encryptedSymbolism: string | null = guide.symbolism ? System.encryptDataWithUserKey(guide.symbolism, userKey) : null; const encryptedMotifs: string | null = guide.motifs ? System.encryptDataWithUserKey(guide.motifs, userKey) : null; const encryptedNarrativeVoice: string | null = guide.narrative_voice ? System.encryptDataWithUserKey(guide.narrative_voice, userKey) : null; const encryptedPacing: string | null = guide.pacing ? System.encryptDataWithUserKey(guide.pacing, userKey) : null; const encryptedIntendedAudience: string | null = guide.intended_audience ? System.encryptDataWithUserKey(guide.intended_audience, userKey) : null; const encryptedKeyMessages: string | null = guide.key_messages ? System.encryptDataWithUserKey(guide.key_messages, userKey) : null; return BookRepo.insertSyncGuideLine(userId, guide.book_id, encryptedGuideTone, encryptedGuideAtmosphere, encryptedWritingStyle, encryptedGuideThemes, encryptedSymbolism, encryptedMotifs, encryptedNarrativeVoice, encryptedPacing, encryptedIntendedAudience, encryptedKeyMessages, guide.last_update, lang); }); if (!guidelinesInserted) return false; return data.issues.every((issue: BookIssuesTable): boolean => { const encryptedIssueName: string = System.encryptDataWithUserKey(issue.name, userKey); return BookRepo.insertSyncIssue(issue.issue_id, userId, issue.book_id, encryptedIssueName, issue.hashed_issue_name, issue.last_update, lang); }); } static async getCompleteSyncBook(userId: string, data: BookSyncCompare, lang: "fr" | "en"):Promise { const userKey: string = getUserEncryptionKey(userId); const bookData: EritBooksTable[] = []; const chaptersData: BookChaptersTable[] = []; const plotPointsData: BookPlotPointsTable[] = []; const incidentsData: BookIncidentsTable[] = []; const chapterContentsData: BookChapterContentTable[] = []; const chapterInfosData: BookChapterInfosTable[] = []; const charactersData: BookCharactersTable[] = []; const characterAttributesData: BookCharactersAttributesTable[] = []; const locationsData: BookLocationTable[] = []; const locationElementsData: LocationElementTable[] = []; const locationSubElementsData: LocationSubElementTable[] = []; const worldsData: BookWorldTable[] = []; const worldElementsData: BookWorldElementsTable[] = []; const actSummariesData: BookActSummariesTable[] = []; const guideLineData: BookGuideLineTable[] = []; const aiGuideLineData: BookAIGuideLineTable[] = []; const issuesData: BookIssuesTable[] = []; const actSummaries: string[] = data.actSummaries; const chapters: string[] = data.chapters; const plotPoints: string[] = data.plotPoints; const incidents: string[] = data.incidents; const chapterContents: string[] = data.chapterContents; const chapterInfos: string[] = data.chapterInfos; const characters: string[] = data.characters; const characterAttributes: string[] = data.characterAttributes; const locations: string[] = data.locations; const locationElements: string[] = data.locationElements; const locationSubElements: string[] = data.locationSubElements; const worlds: string[] = data.worlds; const worldElements: string[] = data.worldElements; const issues: string[] = data.issues; if (actSummaries && actSummaries.length > 0) { for (const id of actSummaries) { const actSummary: BookActSummariesTable[] = await BookRepo.fetchCompleteActSummaryById(id, lang); if (actSummary.length>0) { const actSummaryData: BookActSummariesTable = actSummary[0]; actSummariesData.push({ ...actSummaryData, summary: actSummaryData.summary ? System.decryptDataWithUserKey(actSummaryData.summary, userKey) : null }); } } } if (chapters && chapters.length > 0) { for (const id of chapters) { const chapter: BookChaptersTable[] = await BookRepo.fetchCompleteChapterById(id, lang); if (chapter.length>0) { const chapterData: BookChaptersTable = chapter[0]; chaptersData.push({ ...chapterData, title: System.decryptDataWithUserKey(chapterData.title, userKey) }); } } } if (plotPoints && plotPoints.length > 0) { for (const id of plotPoints) { const plotPoint: BookPlotPointsTable[] = await BookRepo.fetchCompletePlotPointById(id, lang); if (plotPoint.length>0) { const plotPointData: BookPlotPointsTable = plotPoint[0]; plotPointsData.push({ ...plotPointData, title: System.decryptDataWithUserKey(plotPointData.title, userKey), summary: plotPointData.summary ? System.decryptDataWithUserKey(plotPointData.summary, userKey) : null }); } } } if (incidents && incidents.length > 0) { for (const id of incidents) { const incident: BookIncidentsTable[] = await BookRepo.fetchCompleteIncidentById(id, lang); if (incident.length>0) { const incidentData: BookIncidentsTable = incident[0]; incidentsData.push({ ...incidentData, title: System.decryptDataWithUserKey(incidentData.title, userKey), summary: incidentData.summary ? System.decryptDataWithUserKey(incidentData.summary, userKey) : null }); } } } if (chapterContents && chapterContents.length > 0) { for (const id of chapterContents) { const chapterContent: BookChapterContentTable[] = await BookRepo.fetchCompleteChapterContentById(id, lang); if (chapterContent.length>0) { const chapterContentData: BookChapterContentTable = chapterContent[0]; chapterContentsData.push({ ...chapterContentData, content: chapterContentData.content ? JSON.parse(System.decryptDataWithUserKey(chapterContentData.content, userKey)) : null }); } } } if (chapterInfos && chapterInfos.length > 0) { for (const id of chapterInfos) { const chapterInfo: BookChapterInfosTable[] = await BookRepo.fetchCompleteChapterInfoById(id, lang); if (chapterInfo.length>0) { const chapterInfoData: BookChapterInfosTable = chapterInfo[0]; chapterInfosData.push({ ...chapterInfoData, summary: chapterInfoData.summary ? System.decryptDataWithUserKey(chapterInfoData.summary, userKey) : null, goal: chapterInfoData.goal ? System.decryptDataWithUserKey(chapterInfoData.goal, userKey) : null }); } } } if (characters && characters.length > 0) { for (const id of characters) { const character: BookCharactersTable[] = await BookRepo.fetchCompleteCharacterById(id, lang); if (character.length>0) { const characterData: BookCharactersTable = character[0]; charactersData.push({ ...characterData, first_name: System.decryptDataWithUserKey(characterData.first_name, userKey), last_name: characterData.last_name ? System.decryptDataWithUserKey(characterData.last_name, userKey) : null, category: System.decryptDataWithUserKey(characterData.category, userKey), title: characterData.title ? System.decryptDataWithUserKey(characterData.title, userKey) : null, role: characterData.role ? System.decryptDataWithUserKey(characterData.role, userKey) : null, biography: characterData.biography ? System.decryptDataWithUserKey(characterData.biography, userKey) : null, history: characterData.history ? System.decryptDataWithUserKey(characterData.history, userKey) : null }); } } } if (characterAttributes && characterAttributes.length > 0) { for (const id of characterAttributes) { const characterAttribute: BookCharactersAttributesTable[] = await BookRepo.fetchCompleteCharacterAttributeById(id, lang); if (characterAttribute.length>0) { const characterAttributeData: BookCharactersAttributesTable = characterAttribute[0]; characterAttributesData.push({ ...characterAttributeData, attribute_name: System.decryptDataWithUserKey(characterAttributeData.attribute_name, userKey), attribute_value: System.decryptDataWithUserKey(characterAttributeData.attribute_value, userKey) }); } } } if (locations && locations.length > 0) { for (const id of locations) { const location: BookLocationTable[] = await BookRepo.fetchCompleteLocationById(id, lang); if (location.length>0) { const locationData: BookLocationTable = location[0]; locationsData.push({ ...locationData, loc_name: System.decryptDataWithUserKey(locationData.loc_name, userKey) }); } } } if (locationElements && locationElements.length > 0) { for (const id of locationElements) { const locationElement: LocationElementTable[] = await BookRepo.fetchCompleteLocationElementById(id, lang); if (locationElement.length>0) { const locationElementData: LocationElementTable = locationElement[0]; locationElementsData.push({ ...locationElementData, element_name: System.decryptDataWithUserKey(locationElementData.element_name, userKey), element_description: locationElementData.element_description ? System.decryptDataWithUserKey(locationElementData.element_description, userKey) : null }); } } } if (locationSubElements && locationSubElements.length > 0) { for (const id of locationSubElements) { const locationSubElement: LocationSubElementTable[] = await BookRepo.fetchCompleteLocationSubElementById(id, lang); if (locationSubElement.length>0) { const locationSubElementData: LocationSubElementTable = locationSubElement[0]; locationSubElementsData.push({ ...locationSubElementData, sub_elem_name: System.decryptDataWithUserKey(locationSubElementData.sub_elem_name, userKey), sub_elem_description: locationSubElementData.sub_elem_description ? System.decryptDataWithUserKey(locationSubElementData.sub_elem_description, userKey) : null }); } } } if (worlds && worlds.length > 0) { for (const id of worlds) { const world: BookWorldTable[] = await BookRepo.fetchCompleteWorldById(id, lang); if (world.length>0) { const worldData: BookWorldTable = world[0]; worldsData.push({ ...worldData, name: System.decryptDataWithUserKey(worldData.name, userKey), history: worldData.history ? System.decryptDataWithUserKey(worldData.history, userKey) : null, politics: worldData.politics ? System.decryptDataWithUserKey(worldData.politics, userKey) : null, economy: worldData.economy ? System.decryptDataWithUserKey(worldData.economy, userKey) : null, religion: worldData.religion ? System.decryptDataWithUserKey(worldData.religion, userKey) : null, languages: worldData.languages ? System.decryptDataWithUserKey(worldData.languages, userKey) : null }); } } } if (worldElements && worldElements.length > 0) { for (const id of worldElements) { const worldElement: BookWorldElementsTable[] = await BookRepo.fetchCompleteWorldElementById(id, lang); if (worldElement.length>0) { const worldElementData: BookWorldElementsTable = worldElement[0]; worldElementsData.push({ ...worldElementData, name: System.decryptDataWithUserKey(worldElementData.name, userKey), description: worldElementData.description ? System.decryptDataWithUserKey(worldElementData.description, userKey) : null }); } } } if (issues && issues.length > 0) { for (const id of issues) { const issue: BookIssuesTable[] = await BookRepo.fetchCompleteIssueById(id, lang); if (issue.length>0) { const issueData: BookIssuesTable = issue[0]; issuesData.push({ ...issueData, name: System.decryptDataWithUserKey(issueData.name, userKey) }); } } } const book: EritBooksTable[] = await BookRepo.fetchCompleteBookById(data.id, lang); if (book.length>0) { const bookDataItem: EritBooksTable = book[0]; bookData.push({ ...bookDataItem, title: System.decryptDataWithUserKey(bookDataItem.title, userKey), sub_title: bookDataItem.sub_title ? System.decryptDataWithUserKey(bookDataItem.sub_title, userKey) : null, summary: bookDataItem.summary ? System.decryptDataWithUserKey(bookDataItem.summary, userKey) : null, cover_image: bookDataItem.cover_image ? System.decryptDataWithUserKey(bookDataItem.cover_image, userKey) : null }); } return { eritBooks: bookData, chapters: chaptersData, plotPoints: plotPointsData, incidents: incidentsData, chapterContents: chapterContentsData, chapterInfos: chapterInfosData, characters: charactersData, characterAttributes: characterAttributesData, locations: locationsData, locationElements: locationElementsData, locationSubElements: locationSubElementsData, worlds: worldsData, worldElements: worldElementsData, actSummaries: actSummariesData, guideLine: guideLineData, aiGuideLine: aiGuideLineData, issues: issuesData }; } static async syncBookFromServerToClient(userId:string,completeBook: CompleteBook,lang:"fr"|"en"):Promise { const userKey: string = getUserEncryptionKey(userId); const actSummaries: BookActSummariesTable[] = completeBook.actSummaries; const chapters: BookChaptersTable[] = completeBook.chapters; const plotPoints: BookPlotPointsTable[] = completeBook.plotPoints; const incidents: BookIncidentsTable[] = completeBook.incidents; const chapterContents: BookChapterContentTable[] = completeBook.chapterContents; const chapterInfos: BookChapterInfosTable[] = completeBook.chapterInfos; const characters: BookCharactersTable[] = completeBook.characters; const characterAttributes: BookCharactersAttributesTable[] = completeBook.characterAttributes; const locations: BookLocationTable[] = completeBook.locations; const locationElements: LocationElementTable[] = completeBook.locationElements; const locationSubElements: LocationSubElementTable[] = completeBook.locationSubElements; const worlds: BookWorldTable[] = completeBook.worlds; const worldElements: BookWorldElementsTable[] = completeBook.worldElements; const issues: BookIssuesTable[] = completeBook.issues; const bookId: string = completeBook.eritBooks.length > 0 ? completeBook.eritBooks[0].book_id : ''; if (chapters && chapters.length > 0) { for (const chapter of chapters) { const isExist: boolean = ChapterRepo.isChapterExist(userId, chapter.chapter_id,lang); const title: string = System.encryptDataWithUserKey(chapter.title, userKey) if (isExist) { const updated: boolean = ChapterRepo.updateChapter(userId, chapter.chapter_id, title, chapter.hashed_title, chapter.chapter_order, chapter.last_update, lang); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncChapter(chapter.chapter_id, chapter.book_id, userId, title, chapter.hashed_title, chapter.words_count || 0, chapter.chapter_order, chapter.last_update,lang); if (!insert) { return false; } } } } if (actSummaries && actSummaries.length > 0) { for (const act of actSummaries) { const isExist: boolean = BookRepo.actSummarizeExist(userId, bookId, act.act_index,lang); const summary: string = System.encryptDataWithUserKey(act.summary ? act.summary : '', userKey) if (isExist) { const updated: boolean = BookRepo.updateActSummary(userId, bookId, act.act_index, summary, act.last_update,lang); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncActSummary(act.act_sum_id, userId, bookId, act.act_index, summary, act.last_update,lang); if (!insert) { return false; } } } } if (plotPoints && plotPoints.length > 0) { for (const plotPoint of plotPoints) { const title: string = System.encryptDataWithUserKey(plotPoint.title, userKey); const summary: string = System.encryptDataWithUserKey(plotPoint.summary ? plotPoint.summary : '', userKey); const ifExist: boolean = BookRepo.plotPointExist(userId, bookId, plotPoint.plot_point_id,lang); if (ifExist) { const updated: boolean = BookRepo.updatePlotPoint(userId, bookId, plotPoint.plot_point_id, title, plotPoint.hashed_title, summary, plotPoint.last_update,lang); if (!updated) { return false; } } else { if (!plotPoint.linked_incident_id) { return false; } const created: boolean = BookRepo.insertSyncPlotPoint(plotPoint.plot_point_id, title, plotPoint.hashed_title, summary, plotPoint.linked_incident_id, plotPoint.author_id, bookId, plotPoint.last_update,lang); if (!created) { return false; } } } } if (incidents && incidents.length > 0) { for (const incident of incidents) { const title: string = System.encryptDataWithUserKey(incident.title, userKey); const summary: string = System.encryptDataWithUserKey(incident.summary ? incident.summary : '', userKey); const isExist: boolean = BookRepo.incidentExist(userId, bookId, incident.incident_id,lang); if (isExist) { const updated: boolean = BookRepo.updateIncident(userId, bookId, incident.incident_id, title, incident.hashed_title, summary, incident.last_update,lang); if (!updated) { return false; } } else { const created: boolean = BookRepo.insertSyncIncident(incident.incident_id, userId, bookId, title, incident.hashed_title, summary, incident.last_update,lang); if (!created) { return false; } } } } if (chapterContents && chapterContents.length > 0) { for (const chapterContent of chapterContents) { const content: string = System.encryptDataWithUserKey(chapterContent.content ? JSON.stringify(chapterContent.content) : '', userKey); const updated: boolean = ChapterRepo.updateChapterContent(userId, chapterContent.chapter_id, chapterContent.version, content, chapterContent.words_count, chapterContent.last_update); if (!updated) { return false; } } } if (chapterInfos && chapterInfos.length > 0) { for (const chapterInfo of chapterInfos) { const isExist: boolean = ChapterRepo.isChapterInfoExist(userId, chapterInfo.chapter_id,lang); const summary: string = System.encryptDataWithUserKey(chapterInfo.summary ? chapterInfo.summary : '', userKey); const goal: string = System.encryptDataWithUserKey(chapterInfo.goal ? chapterInfo.goal : '', userKey); if (isExist) { const updated: boolean = ChapterRepo.updateChapterInfos(userId, chapterInfo.chapter_id, chapterInfo.act_id, bookId, chapterInfo.incident_id, chapterInfo.plot_point_id, summary, goal, chapterInfo.last_update,lang); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncChapterInfo(chapterInfo.chapter_info_id, chapterInfo.chapter_id, chapterInfo.act_id, chapterInfo.incident_id, chapterInfo.plot_point_id, bookId, chapterInfo.author_id, chapterInfo.summary, chapterInfo.goal, chapterInfo.last_update,lang); if (!insert) { return false; } } } } if (characters && characters.length > 0) { for (const character of characters) { const isExist: boolean = CharacterRepo.isCharacterExist(userId, character.character_id,lang); const firstName: string = System.encryptDataWithUserKey(character.first_name, userKey); const lastName: string = System.encryptDataWithUserKey(character.last_name ? character.last_name : '', userKey); const category: string = System.encryptDataWithUserKey(character.category, userKey); const title: string = System.encryptDataWithUserKey(character.title ? character.title : '', userKey); const role: string = System.encryptDataWithUserKey(character.role ? character.role : '', userKey); const image: string = System.encryptDataWithUserKey(character.image ? character.image : '', userKey); const biography: string = System.encryptDataWithUserKey(character.biography ? character.biography : '', userKey); const history: string = System.encryptDataWithUserKey(character.history ? character.history : '', userKey); if (isExist) { const updated: boolean = CharacterRepo.updateCharacter(userId, character.character_id, firstName, lastName, title, category, image, role, biography, history, character.last_update); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncCharacter(character.character_id, bookId, userId, firstName, lastName, category, title, image, role, biography, history, character.last_update,lang); if (!insert) { return false; } } } } if (characterAttributes && characterAttributes.length > 0) { for (const characterAttribute of characterAttributes) { const isExist: boolean = CharacterRepo.isCharacterAttributeExist(userId, characterAttribute.attr_id,lang); const attributeName: string = System.encryptDataWithUserKey(characterAttribute.attribute_name, userKey); const attributeValue: string = System.encryptDataWithUserKey(characterAttribute.attribute_value, userKey); if (isExist) { const updated: boolean = CharacterRepo.updateCharacterAttribute(userId, characterAttribute.attr_id, attributeName, attributeValue, characterAttribute.last_update,lang); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncCharacterAttribute(characterAttribute.attr_id, characterAttribute.character_id, userId, attributeName, attributeValue, characterAttribute.last_update,lang); if (!insert) { return false; } } } } if (locations && locations.length > 0) { for (const location of locations) { const isExist: boolean = LocationRepo.isLocationExist(userId, location.loc_id,lang); const locName: string = System.encryptDataWithUserKey(location.loc_name, userKey); if (isExist) { const updated: boolean = LocationRepo.updateLocationSection(userId, location.loc_id, locName, location.loc_original_name, location.last_update,lang); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncLocation(location.loc_id, bookId, userId, locName, location.loc_original_name, location.last_update,lang); if (!insert) { return false; } } } } if (locationElements && locationElements.length > 0) { for (const locationElement of locationElements) { const isExist: boolean = LocationRepo.isLocationElementExist(userId, locationElement.element_id,lang); const elementName: string = System.encryptDataWithUserKey(locationElement.element_name, userKey); const elementDescription: string = System.encryptDataWithUserKey(locationElement.element_description ? locationElement.element_description : '', userKey); if (isExist) { const updated: boolean = LocationRepo.updateLocationElement(userId, locationElement.element_id, elementName, locationElement.original_name, elementDescription, locationElement.last_update,lang); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncLocationElement(locationElement.element_id, locationElement.location, userId, elementName, locationElement.original_name, elementDescription, locationElement.last_update,lang); if (!insert) { return false; } } } } if (locationSubElements && locationSubElements.length > 0) { for (const locationSubElement of locationSubElements) { const isExist: boolean = LocationRepo.isLocationSubElementExist(userId, locationSubElement.sub_element_id,lang); const subElemName: string = System.encryptDataWithUserKey(locationSubElement.sub_elem_name, userKey); const subElemDescription: string = System.encryptDataWithUserKey(locationSubElement.sub_elem_description ? locationSubElement.sub_elem_description : '', userKey); if (isExist) { const updated: boolean = LocationRepo.updateLocationSubElement(userId, locationSubElement.sub_element_id, subElemName, locationSubElement.original_name, subElemDescription, locationSubElement.last_update,lang); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncLocationSubElement(locationSubElement.sub_element_id, locationSubElement.element_id, userId, subElemName, locationSubElement.original_name, subElemDescription, locationSubElement.last_update,lang); if (!insert) { return false; } } } } if (worlds && worlds.length > 0) { for (const world of worlds) { const isExist: boolean = BookRepo.worldExist(userId, bookId, world.world_id,lang); const name: string = System.encryptDataWithUserKey(world.name, userKey); const history: string = System.encryptDataWithUserKey(world.history ? world.history : '', userKey); const politics: string = System.encryptDataWithUserKey(world.politics ? world.politics : '', userKey); const economy: string = System.encryptDataWithUserKey(world.economy ? world.economy : '', userKey); const religion: string = System.encryptDataWithUserKey(world.religion ? world.religion : '', userKey); const languages: string = System.encryptDataWithUserKey(world.languages ? world.languages : '', userKey); if (isExist) { const updated: boolean = BookRepo.updateWorld(userId, world.world_id, name, world.hashed_name, history, politics, economy, religion, languages, world.last_update,lang); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncWorld(world.world_id, name, world.hashed_name, userId, bookId, history, politics, economy, religion, languages, world.last_update,lang); if (!insert) { return false; } } } } if (worldElements && worldElements.length > 0) { for (const worldElement of worldElements) { const isExist: boolean = BookRepo.worldElementExist(userId, worldElement.world_id, worldElement.element_id,lang); const name: string = System.encryptDataWithUserKey(worldElement.name, userKey); const description: string = System.encryptDataWithUserKey(worldElement.description ? worldElement.description : '', userKey); if (isExist) { const updated: boolean = BookRepo.updateWorldElement(userId, worldElement.element_id, name, description, worldElement.last_update,lang); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncWorldElement(worldElement.element_id, worldElement.world_id, userId, worldElement.element_type, name, worldElement.original_name, description, worldElement.last_update,lang); if (!insert) { return false; } } } } if (issues && issues.length > 0) { for (const issue of issues) { const isExist: boolean = BookRepo.issueExist(userId, bookId, issue.issue_id,lang); const name: string = System.encryptDataWithUserKey(issue.name, userKey); if (isExist) { const updated: boolean = BookRepo.updateIssue(userId, bookId, issue.issue_id, name, issue.hashed_issue_name, issue.last_update, lang); if (!updated) { return false; } } else { const insert: boolean = BookRepo.insertSyncIssue(issue.issue_id, userId, bookId, name, issue.hashed_issue_name, issue.last_update, lang); if (!insert) { return false; } } } } return true; } }