import {getUserEncryptionKey} from "../keyManager.js"; import System from "../System.js"; import {CompleteBook} from "./Book.js"; import BookRepo, {EritBooksTable, BookToolsTable} from "../repositories/book.repository.js"; import ChapterRepo, { BookChapterInfosTable, BookChaptersTable } from "../repositories/chapter.repository.js"; import IncidentRepository, {BookIncidentsTable} from "../repositories/incident.repository.js"; import PlotPointRepository, {BookPlotPointsTable} from "../repositories/plotpoint.repository.js"; import ChapterContentRepository, {BookChapterContentTable} from "../repositories/chaptercontent.repository.js"; import CharacterRepo, { BookCharactersAttributesTable, BookCharactersTable } from "../repositories/character.repository.js"; import LocationRepo, { BookLocationTable, LocationElementTable, LocationSubElementTable } from "../repositories/location.repository.js"; import WorldRepository, { BookWorldElementsTable, BookWorldTable } from "../repositories/world.repository.js"; import ActRepository, {BookActSummariesTable} from "../repositories/act.repository.js"; import GuidelineRepo, { BookAIGuideLineTable, BookGuideLineTable } from "../repositories/guideline.repository.js"; import IssueRepository, {BookIssuesTable} from "../repositories/issue.repository.js"; export default class Download { /** * Saves a complete book with all its associated data to the local database. * This method encrypts all sensitive data using the user's encryption key before storing. * It processes and inserts all book components including chapters, incidents, plot points, * chapter contents, chapter infos, characters, character attributes, locations, location elements, * location sub-elements, worlds, world elements, act summaries, AI guidelines, guidelines, and issues. * * @param userId - The unique identifier of the user who owns the book * @param data - The complete book data structure containing all book components to save * @param lang - The language code for localization ("fr" for French or "en" for English) * @returns A promise that resolves to true if all data was saved successfully, false otherwise */ static async saveCompleteBook(userId: string, data: CompleteBook, lang: "fr" | "en"): Promise { const userEncryptionKey: string = getUserEncryptionKey(userId); const bookData: EritBooksTable = data.eritBooks[0]; const encryptedBookTitle: string = System.encryptDataWithUserKey(bookData.title, userEncryptionKey); const encryptedBookSubTitle: string | null = bookData.sub_title ? System.encryptDataWithUserKey(bookData.sub_title, userEncryptionKey) : null; const encryptedBookSummary: string | null = bookData.summary ? System.encryptDataWithUserKey(bookData.summary, userEncryptionKey) : null; const encryptedBookCoverImage: string | null = bookData.cover_image ? System.encryptDataWithUserKey(bookData.cover_image, userEncryptionKey) : null; const bookInserted: boolean = BookRepo.insertSyncBook( bookData.book_id, userId, bookData.type, encryptedBookTitle, bookData.hashed_title, encryptedBookSubTitle, bookData.hashed_sub_title, encryptedBookSummary, bookData.serie_id, bookData.desired_release_date, bookData.desired_word_count, bookData.words_count, encryptedBookCoverImage, bookData.last_update, lang ); if (!bookInserted) return false; const chaptersInserted: boolean = data.chapters.every((chapter: BookChaptersTable): boolean => { const encryptedChapterTitle: string = System.encryptDataWithUserKey(chapter.title, userEncryptionKey); return ChapterRepo.insertSyncChapter(chapter.chapter_id, chapter.book_id, userId, encryptedChapterTitle, 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, userEncryptionKey); const encryptedIncidentSummary: string | null = incident.summary ? System.encryptDataWithUserKey(incident.summary, userEncryptionKey) : null; return IncidentRepository.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 encryptedPlotPointTitle: string = System.encryptDataWithUserKey(plotPoint.title, userEncryptionKey); const encryptedPlotPointSummary: string | null = plotPoint.summary ? System.encryptDataWithUserKey(plotPoint.summary, userEncryptionKey) : null; return PlotPointRepository.insertSyncPlotPoint(plotPoint.plot_point_id, encryptedPlotPointTitle, plotPoint.hashed_title, encryptedPlotPointSummary, plotPoint.linked_incident_id, userId, plotPoint.book_id, plotPoint.last_update, lang); }); if (!plotPointsInserted) return false; const chapterContentsInserted: boolean = data.chapterContents.every((chapterContent: BookChapterContentTable): boolean => { const encryptedChapterContent: string | null = chapterContent.content ? System.encryptDataWithUserKey(JSON.stringify(chapterContent.content), userEncryptionKey) : null; return ChapterContentRepository.insertSyncChapterContent(chapterContent.content_id, chapterContent.chapter_id, userId, chapterContent.version, encryptedChapterContent, chapterContent.words_count, chapterContent.time_on_it, chapterContent.last_update, lang); }); if (!chapterContentsInserted) return false; const chapterInfosInserted: boolean = data.chapterInfos.every((chapterInfo: BookChapterInfosTable): boolean => { const encryptedChapterSummary: string | null = chapterInfo.summary ? System.encryptDataWithUserKey(chapterInfo.summary, userEncryptionKey) : null; const encryptedChapterGoal: string | null = chapterInfo.goal ? System.encryptDataWithUserKey(chapterInfo.goal, userEncryptionKey) : null; return ChapterRepo.insertSyncChapterInfo(chapterInfo.chapter_info_id, chapterInfo.chapter_id, chapterInfo.act_id, chapterInfo.incident_id, chapterInfo.plot_point_id, chapterInfo.book_id, userId, encryptedChapterSummary, encryptedChapterGoal, chapterInfo.last_update, lang); }); if (!chapterInfosInserted) return false; const charactersInserted: boolean = data.characters.every((character: BookCharactersTable): boolean => { const encryptedCharacterFirstName: string = System.encryptDataWithUserKey(character.first_name, userEncryptionKey); const encryptedCharacterLastName: string | null = character.last_name ? System.encryptDataWithUserKey(character.last_name, userEncryptionKey) : null; const encryptedCharacterCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey); const encryptedCharacterTitle: string | null = character.title ? System.encryptDataWithUserKey(character.title, userEncryptionKey) : null; const encryptedCharacterImage: string | null = character.image ? System.encryptDataWithUserKey(character.image, userEncryptionKey) : null; const encryptedCharacterRole: string | null = character.role ? System.encryptDataWithUserKey(character.role, userEncryptionKey) : null; const encryptedCharacterBiography: string | null = character.biography ? System.encryptDataWithUserKey(character.biography, userEncryptionKey) : null; const encryptedCharacterHistory: string | null = character.history ? System.encryptDataWithUserKey(character.history, userEncryptionKey) : null; return CharacterRepo.insertSyncCharacter(character.character_id, character.book_id, userId, encryptedCharacterFirstName, encryptedCharacterLastName, encryptedCharacterCategory, encryptedCharacterTitle, encryptedCharacterImage, encryptedCharacterRole, encryptedCharacterBiography, encryptedCharacterHistory, character.last_update, lang); }); if (!charactersInserted) return false; const characterAttributesInserted: boolean = data.characterAttributes.every((characterAttribute: BookCharactersAttributesTable): boolean => { const encryptedAttributeName: string = System.encryptDataWithUserKey(characterAttribute.attribute_name, userEncryptionKey); const encryptedAttributeValue: string = System.encryptDataWithUserKey(characterAttribute.attribute_value, userEncryptionKey); return CharacterRepo.insertSyncCharacterAttribute(characterAttribute.attr_id, characterAttribute.character_id, userId, encryptedAttributeName, encryptedAttributeValue, characterAttribute.last_update, lang); }); if (!characterAttributesInserted) return false; const locationsInserted: boolean = data.locations.every((location: BookLocationTable): boolean => { const encryptedLocationName: string = System.encryptDataWithUserKey(location.loc_name, userEncryptionKey); return LocationRepo.insertSyncLocation(location.loc_id, location.book_id, userId, encryptedLocationName, location.loc_original_name, location.last_update, lang); }); if (!locationsInserted) return false; const locationElementsInserted: boolean = data.locationElements.every((locationElement: LocationElementTable): boolean => { const encryptedLocationElementName: string = System.encryptDataWithUserKey(locationElement.element_name, userEncryptionKey); const encryptedLocationElementDescription: string | null = locationElement.element_description ? System.encryptDataWithUserKey(locationElement.element_description, userEncryptionKey) : null; return LocationRepo.insertSyncLocationElement(locationElement.element_id, locationElement.location, userId, encryptedLocationElementName, locationElement.original_name, encryptedLocationElementDescription, locationElement.last_update, lang); }); if (!locationElementsInserted) return false; const locationSubElementsInserted: boolean = data.locationSubElements.every((locationSubElement: LocationSubElementTable): boolean => { const encryptedSubElementName: string = System.encryptDataWithUserKey(locationSubElement.sub_elem_name, userEncryptionKey); const encryptedSubElementDescription: string | null = locationSubElement.sub_elem_description ? System.encryptDataWithUserKey(locationSubElement.sub_elem_description, userEncryptionKey) : null; return LocationRepo.insertSyncLocationSubElement(locationSubElement.sub_element_id, locationSubElement.element_id, userId, encryptedSubElementName, locationSubElement.original_name, encryptedSubElementDescription, locationSubElement.last_update, lang); }); if (!locationSubElementsInserted) return false; const worldsInserted: boolean = data.worlds.every((world: BookWorldTable): boolean => { const encryptedWorldName: string = System.encryptDataWithUserKey(world.name, userEncryptionKey); const encryptedWorldHistory: string | null = world.history ? System.encryptDataWithUserKey(world.history, userEncryptionKey) : null; const encryptedWorldPolitics: string | null = world.politics ? System.encryptDataWithUserKey(world.politics, userEncryptionKey) : null; const encryptedWorldEconomy: string | null = world.economy ? System.encryptDataWithUserKey(world.economy, userEncryptionKey) : null; const encryptedWorldReligion: string | null = world.religion ? System.encryptDataWithUserKey(world.religion, userEncryptionKey) : null; const encryptedWorldLanguages: string | null = world.languages ? System.encryptDataWithUserKey(world.languages, userEncryptionKey) : null; return WorldRepository.insertSyncWorld(world.world_id, encryptedWorldName, world.hashed_name, userId, world.book_id, encryptedWorldHistory, encryptedWorldPolitics, encryptedWorldEconomy, encryptedWorldReligion, encryptedWorldLanguages, world.last_update, lang); }); if (!worldsInserted) return false; const worldElementsInserted: boolean = data.worldElements.every((worldElement: BookWorldElementsTable): boolean => { const encryptedWorldElementName: string = System.encryptDataWithUserKey(worldElement.name, userEncryptionKey); const encryptedWorldElementDescription: string | null = worldElement.description ? System.encryptDataWithUserKey(worldElement.description, userEncryptionKey) : null; return WorldRepository.insertSyncWorldElement(worldElement.element_id, worldElement.world_id, userId, worldElement.element_type, encryptedWorldElementName, worldElement.original_name, encryptedWorldElementDescription, worldElement.last_update, lang); }); if (!worldElementsInserted) return false; const actSummariesInserted: boolean = data.actSummaries.every((actSummary: BookActSummariesTable): boolean => { const encryptedActSummary: string | null = actSummary.summary ? System.encryptDataWithUserKey(actSummary.summary, userEncryptionKey) : null; return ActRepository.insertSyncActSummary(actSummary.act_sum_id, actSummary.book_id, userId, actSummary.act_index, encryptedActSummary, actSummary.last_update, lang); }); if (!actSummariesInserted) return false; const aiGuidelinesInserted: boolean = data.aiGuideLine.every((aiGuideline: BookAIGuideLineTable): boolean => { const encryptedAIGlobalResume: string | null = aiGuideline.global_resume ? System.encryptDataWithUserKey(aiGuideline.global_resume, userEncryptionKey) : null; const encryptedAIThemes: string | null = aiGuideline.themes ? System.encryptDataWithUserKey(aiGuideline.themes, userEncryptionKey) : null; const encryptedAITone: string | null = aiGuideline.tone ? System.encryptDataWithUserKey(aiGuideline.tone, userEncryptionKey) : null; const encryptedAIAtmosphere: string | null = aiGuideline.atmosphere ? System.encryptDataWithUserKey(aiGuideline.atmosphere, userEncryptionKey) : null; const encryptedAICurrentResume: string | null = aiGuideline.current_resume ? System.encryptDataWithUserKey(aiGuideline.current_resume, userEncryptionKey) : null; return GuidelineRepo.insertSyncAIGuideLine(userId, aiGuideline.book_id, encryptedAIGlobalResume, encryptedAIThemes, aiGuideline.verbe_tense, aiGuideline.narrative_type, aiGuideline.langue, aiGuideline.dialogue_type, encryptedAITone, encryptedAIAtmosphere, encryptedAICurrentResume, aiGuideline.last_update, lang); }); if (!aiGuidelinesInserted) return false; const guidelinesInserted: boolean = data.guideLine.every((guideline: BookGuideLineTable): boolean => { const encryptedGuidelineTone: string | null = guideline.tone ? System.encryptDataWithUserKey(guideline.tone, userEncryptionKey) : null; const encryptedGuidelineAtmosphere: string | null = guideline.atmosphere ? System.encryptDataWithUserKey(guideline.atmosphere, userEncryptionKey) : null; const encryptedGuidelineWritingStyle: string | null = guideline.writing_style ? System.encryptDataWithUserKey(guideline.writing_style, userEncryptionKey) : null; const encryptedGuidelineThemes: string | null = guideline.themes ? System.encryptDataWithUserKey(guideline.themes, userEncryptionKey) : null; const encryptedGuidelineSymbolism: string | null = guideline.symbolism ? System.encryptDataWithUserKey(guideline.symbolism, userEncryptionKey) : null; const encryptedGuidelineMotifs: string | null = guideline.motifs ? System.encryptDataWithUserKey(guideline.motifs, userEncryptionKey) : null; const encryptedGuidelineNarrativeVoice: string | null = guideline.narrative_voice ? System.encryptDataWithUserKey(guideline.narrative_voice, userEncryptionKey) : null; const encryptedGuidelinePacing: string | null = guideline.pacing ? System.encryptDataWithUserKey(guideline.pacing, userEncryptionKey) : null; const encryptedGuidelineIntendedAudience: string | null = guideline.intended_audience ? System.encryptDataWithUserKey(guideline.intended_audience, userEncryptionKey) : null; const encryptedGuidelineKeyMessages: string | null = guideline.key_messages ? System.encryptDataWithUserKey(guideline.key_messages, userEncryptionKey) : null; return GuidelineRepo.insertSyncGuideLine(userId, guideline.book_id, encryptedGuidelineTone, encryptedGuidelineAtmosphere, encryptedGuidelineWritingStyle, encryptedGuidelineThemes, encryptedGuidelineSymbolism, encryptedGuidelineMotifs, encryptedGuidelineNarrativeVoice, encryptedGuidelinePacing, encryptedGuidelineIntendedAudience, encryptedGuidelineKeyMessages, guideline.last_update, lang); }); if (!guidelinesInserted) return false; const issuesInserted: boolean = data.issues.every((issue: BookIssuesTable): boolean => { const encryptedIssueName: string = System.encryptDataWithUserKey(issue.name, userEncryptionKey); return IssueRepository.insertSyncIssue(issue.issue_id, userId, issue.book_id, encryptedIssueName, issue.hashed_issue_name, issue.last_update, lang); }); if (!issuesInserted) return false; return data.bookTools.every((bookTool: BookToolsTable): boolean => { return BookRepo.insertSyncBookTools(bookTool.book_id, userId, bookTool.characters_enabled, bookTool.worlds_enabled, bookTool.locations_enabled, bookTool.last_update, lang); }); } }