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"; import SpellRepo, {BookSpellsTable} from "../repositories/spell.repo.js"; import SpellTagRepo, {BookSpellTagsTable} from "../repositories/spelltag.repo.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 characterData = { firstName: System.encryptDataWithUserKey(character.first_name, userEncryptionKey), lastName: character.last_name ? System.encryptDataWithUserKey(character.last_name, userEncryptionKey) : null, nickname: character.nickname ? System.encryptDataWithUserKey(character.nickname, userEncryptionKey) : null, age: character.age ? System.encryptDataWithUserKey(character.age, userEncryptionKey) : null, gender: character.gender ? System.encryptDataWithUserKey(character.gender, userEncryptionKey) : null, species: character.species ? System.encryptDataWithUserKey(character.species, userEncryptionKey) : null, nationality: character.nationality ? System.encryptDataWithUserKey(character.nationality, userEncryptionKey) : null, status: character.status ? System.encryptDataWithUserKey(character.status, userEncryptionKey) : null, category: System.encryptDataWithUserKey(character.category, userEncryptionKey), title: character.title ? System.encryptDataWithUserKey(character.title, userEncryptionKey) : null, image: character.image ? System.encryptDataWithUserKey(character.image, userEncryptionKey) : null, role: character.role ? System.encryptDataWithUserKey(character.role, userEncryptionKey) : null, biography: character.biography ? System.encryptDataWithUserKey(character.biography, userEncryptionKey) : null, history: character.history ? System.encryptDataWithUserKey(character.history, userEncryptionKey) : null, speechPattern: character.speech_pattern ? System.encryptDataWithUserKey(character.speech_pattern, userEncryptionKey) : null, catchphrase: character.catchphrase ? System.encryptDataWithUserKey(character.catchphrase, userEncryptionKey) : null, residence: character.residence ? System.encryptDataWithUserKey(character.residence, userEncryptionKey) : null, notes: character.notes ? System.encryptDataWithUserKey(character.notes, userEncryptionKey) : null, color: character.color ? System.encryptDataWithUserKey(character.color, userEncryptionKey) : null }; return CharacterRepo.insertSyncCharacter(character.character_id, character.book_id, userId, characterData, 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; const bookToolsInserted: boolean = data.bookTools.every((bookTool: BookToolsTable): boolean => { return BookRepo.insertSyncBookTools(bookTool.book_id, userId, bookTool.characters_enabled, bookTool.worlds_enabled, bookTool.locations_enabled, bookTool.spells_enabled, bookTool.last_update, lang); }); if (!bookToolsInserted) return false; const spellTagsInserted: boolean = data.spellTags.every((spellTag: BookSpellTagsTable): boolean => { const encryptedTagName: string = System.encryptDataWithUserKey(spellTag.name, userEncryptionKey); return SpellTagRepo.insertSyncSpellTag( spellTag.tag_id, spellTag.book_id, userId, encryptedTagName, spellTag.name_hash, spellTag.color, spellTag.last_update, lang ); }); if (!spellTagsInserted) return false; const spellsInserted: boolean = data.spells.every((spell: BookSpellsTable): boolean => { const encryptedName: string = System.encryptDataWithUserKey(spell.name, userEncryptionKey); const encryptedDescription: string = System.encryptDataWithUserKey(spell.description, userEncryptionKey); const encryptedAppearance: string = System.encryptDataWithUserKey(spell.appearance, userEncryptionKey); const encryptedTags: string = System.encryptDataWithUserKey(spell.tags, userEncryptionKey); const encryptedPowerLevel: string | null = spell.power_level ? System.encryptDataWithUserKey(spell.power_level, userEncryptionKey) : null; const encryptedComponents: string | null = spell.components ? System.encryptDataWithUserKey(spell.components, userEncryptionKey) : null; const encryptedLimitations: string | null = spell.limitations ? System.encryptDataWithUserKey(spell.limitations, userEncryptionKey) : null; const encryptedNotes: string | null = spell.notes ? System.encryptDataWithUserKey(spell.notes, userEncryptionKey) : null; return SpellRepo.insertSyncSpell( spell.spell_id, spell.book_id, userId, encryptedName, spell.name_hash, encryptedDescription, encryptedAppearance, encryptedTags, encryptedPowerLevel, encryptedComponents, encryptedLimitations, encryptedNotes, spell.last_update, lang ); }); if (!spellsInserted) return false; return true; } }