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 ActRepository, { BookActSummariesTable } from "../repositories/act.repository.js"; import GuidelineRepo, { BookAIGuideLineTable, BookGuideLineTable } from "../repositories/guideline.repository.js"; import ChapterRepo, { BookChapterInfosTable, BookChaptersTable } from "../repositories/chapter.repository.js"; import CharacterRepo, { BookCharactersAttributesTable, BookCharactersTable } from "../repositories/character.repository.js"; import IncidentRepository, { BookIncidentsTable } from "../repositories/incident.repository.js"; import IssueRepository, { BookIssuesTable } from "../repositories/issue.repository.js"; import LocationRepo, { BookLocationTable, LocationElementTable, LocationSubElementTable } from "../repositories/location.repository.js"; import PlotPointRepository, { BookPlotPointsTable } from "../repositories/plotpoint.repository.js"; import WorldRepository, { BookWorldElementsTable, BookWorldTable } from "../repositories/world.repository.js"; import ChapterContentRepository, { BookChapterContentTable } from "../repositories/chaptercontent.repository.js"; import SpellRepo, { BookSpellsTable } from "../repositories/spell.repo.js"; import SpellTagRepo, { BookSpellTagsTable } from "../repositories/spelltag.repo.js"; export default class Upload { /** * Prepares a complete book with all related data for synchronization upload. * Fetches all book-related tables from the database, decrypts encrypted fields * using the user's encryption key, and returns a complete book object ready for sync. * * @param userId - The unique identifier of the user who owns the book * @param bookId - The unique identifier of the book to upload * @param lang - The language code for localization ("fr" or "en") * @returns A promise that resolves to a CompleteBook object containing all decrypted book data */ static async uploadBookForSync(userId: string, bookId: string, lang: "fr" | "en"): Promise { const userEncryptionKey: string = getUserEncryptionKey(userId); const [ encryptedBooks, encryptedActSummaries, encryptedAIGuidelines, encryptedChapters, encryptedCharacters, encryptedGuidelines, encryptedIncidents, encryptedIssues, encryptedLocations, encryptedPlotPoints, encryptedWorlds, bookToolsData, encryptedSpells, encryptedSpellTags ]: [ EritBooksTable[], BookActSummariesTable[], BookAIGuideLineTable[], BookChaptersTable[], BookCharactersTable[], BookGuideLineTable[], BookIncidentsTable[], BookIssuesTable[], BookLocationTable[], BookPlotPointsTable[], BookWorldTable[], BookToolsTable | null, BookSpellsTable[], BookSpellTagsTable[] ] = await Promise.all([ BookRepo.fetchEritBooksTable(userId, bookId, lang), ActRepository.fetchBookActSummaries(userId, bookId, lang), GuidelineRepo.fetchBookAIGuideLine(userId, bookId, lang), ChapterRepo.fetchBookChapters(userId, bookId, lang), CharacterRepo.fetchBookCharacters(userId, bookId, lang), GuidelineRepo.fetchBookGuideLineTable(userId, bookId, lang), IncidentRepository.fetchBookIncidents(userId, bookId, lang), IssueRepository.fetchBookIssues(userId, bookId, lang), LocationRepo.fetchBookLocations(userId, bookId, lang), PlotPointRepository.fetchBookPlotPoints(userId, bookId, lang), WorldRepository.fetchBookWorlds(userId, bookId, lang), BookRepo.fetchBookTools(userId, bookId, lang), SpellRepo.fetchBookSpellsTable(userId, bookId, lang), SpellTagRepo.fetchBookSpellTagsTable(userId, bookId, lang) ]); const [ nestedChapterContents, nestedChapterInfos, nestedCharacterAttributes, nestedWorldElements, nestedLocationElements ]: [ BookChapterContentTable[][], BookChapterInfosTable[][], BookCharactersAttributesTable[][], BookWorldElementsTable[][], LocationElementTable[][] ] = await Promise.all([ Promise.all(encryptedChapters.map((chapter: BookChaptersTable): Promise => ChapterContentRepository.fetchBookChapterContents(userId, chapter.chapter_id, lang))), Promise.all(encryptedChapters.map((chapter: BookChaptersTable): Promise => ChapterRepo.fetchBookChapterInfos(userId, chapter.chapter_id, lang))), Promise.all(encryptedCharacters.map((character: BookCharactersTable): Promise => CharacterRepo.fetchBookCharactersAttributes(userId, character.character_id, lang))), Promise.all(encryptedWorlds.map((world: BookWorldTable): Promise => WorldRepository.fetchBookWorldElements(userId, world.world_id, lang))), Promise.all(encryptedLocations.map((location: BookLocationTable): Promise => LocationRepo.fetchLocationElements(userId, location.loc_id, lang))) ]); const encryptedChapterContents: BookChapterContentTable[] = nestedChapterContents.flat(); const encryptedChapterInfos: BookChapterInfosTable[] = nestedChapterInfos.flat(); const encryptedCharacterAttributes: BookCharactersAttributesTable[] = nestedCharacterAttributes.flat(); const encryptedWorldElements: BookWorldElementsTable[] = nestedWorldElements.flat(); const encryptedLocationElements: LocationElementTable[] = nestedLocationElements.flat(); const nestedLocationSubElements: LocationSubElementTable[][] = await Promise.all( encryptedLocationElements.map((element: LocationElementTable): Promise => LocationRepo.fetchLocationSubElements(userId, element.element_id, lang)) ); const encryptedLocationSubElements: LocationSubElementTable[] = nestedLocationSubElements.flat(); const eritBooks: EritBooksTable[] = encryptedBooks.map((book: EritBooksTable): EritBooksTable => ({ ...book, title: System.decryptDataWithUserKey(book.title, userEncryptionKey), sub_title: book.sub_title ? System.decryptDataWithUserKey(book.sub_title, userEncryptionKey) : null, summary: book.summary ? System.decryptDataWithUserKey(book.summary, userEncryptionKey) : null, cover_image: book.cover_image ? System.decryptDataWithUserKey(book.cover_image, userEncryptionKey) : null })); const actSummaries: BookActSummariesTable[] = encryptedActSummaries.map((actSummary: BookActSummariesTable): BookActSummariesTable => ({ ...actSummary, summary: actSummary.summary ? System.decryptDataWithUserKey(actSummary.summary, userEncryptionKey) : null })); const aiGuideLine: BookAIGuideLineTable[] = encryptedAIGuidelines.map((guideLine: BookAIGuideLineTable): BookAIGuideLineTable => ({ ...guideLine, global_resume: guideLine.global_resume ? System.decryptDataWithUserKey(guideLine.global_resume, userEncryptionKey) : null, themes: guideLine.themes ? System.decryptDataWithUserKey(guideLine.themes, userEncryptionKey) : null, tone: guideLine.tone ? System.decryptDataWithUserKey(guideLine.tone, userEncryptionKey) : null, atmosphere: guideLine.atmosphere ? System.decryptDataWithUserKey(guideLine.atmosphere, userEncryptionKey) : null, current_resume: guideLine.current_resume ? System.decryptDataWithUserKey(guideLine.current_resume, userEncryptionKey) : null })); const chapters: BookChaptersTable[] = encryptedChapters.map((chapter: BookChaptersTable): BookChaptersTable => ({ ...chapter, title: System.decryptDataWithUserKey(chapter.title, userEncryptionKey) })); const chapterContents: BookChapterContentTable[] = encryptedChapterContents.map((chapterContent: BookChapterContentTable): BookChapterContentTable => ({ ...chapterContent, content: chapterContent.content ? JSON.parse(System.decryptDataWithUserKey(chapterContent.content, userEncryptionKey)) : null })); const chapterInfos: BookChapterInfosTable[] = encryptedChapterInfos.map((chapterInfo: BookChapterInfosTable): BookChapterInfosTable => ({ ...chapterInfo, summary: chapterInfo.summary ? System.decryptDataWithUserKey(chapterInfo.summary, userEncryptionKey) : null, goal: chapterInfo.goal ? System.decryptDataWithUserKey(chapterInfo.goal, userEncryptionKey) : null })); const characters: BookCharactersTable[] = encryptedCharacters.map((character: BookCharactersTable): BookCharactersTable => ({ ...character, first_name: System.decryptDataWithUserKey(character.first_name, userEncryptionKey), last_name: character.last_name ? System.decryptDataWithUserKey(character.last_name, userEncryptionKey) : null, nickname: character.nickname ? System.decryptDataWithUserKey(character.nickname, userEncryptionKey) : null, age: character.age ? System.decryptDataWithUserKey(character.age, userEncryptionKey) : null, gender: character.gender ? System.decryptDataWithUserKey(character.gender, userEncryptionKey) : null, species: character.species ? System.decryptDataWithUserKey(character.species, userEncryptionKey) : null, nationality: character.nationality ? System.decryptDataWithUserKey(character.nationality, userEncryptionKey) : null, status: character.status ? System.decryptDataWithUserKey(character.status, userEncryptionKey) : null, category: System.decryptDataWithUserKey(character.category, userEncryptionKey), title: character.title ? System.decryptDataWithUserKey(character.title, userEncryptionKey) : null, role: character.role ? System.decryptDataWithUserKey(character.role, userEncryptionKey) : null, biography: character.biography ? System.decryptDataWithUserKey(character.biography, userEncryptionKey) : null, history: character.history ? System.decryptDataWithUserKey(character.history, userEncryptionKey) : null, speech_pattern: character.speech_pattern ? System.decryptDataWithUserKey(character.speech_pattern, userEncryptionKey) : null, catchphrase: character.catchphrase ? System.decryptDataWithUserKey(character.catchphrase, userEncryptionKey) : null, residence: character.residence ? System.decryptDataWithUserKey(character.residence, userEncryptionKey) : null, notes: character.notes ? System.decryptDataWithUserKey(character.notes, userEncryptionKey) : null, color: character.color ? System.decryptDataWithUserKey(character.color, userEncryptionKey) : null })); const characterAttributes: BookCharactersAttributesTable[] = encryptedCharacterAttributes.map((attribute: BookCharactersAttributesTable): BookCharactersAttributesTable => ({ ...attribute, attribute_name: System.decryptDataWithUserKey(attribute.attribute_name, userEncryptionKey), attribute_value: System.decryptDataWithUserKey(attribute.attribute_value, userEncryptionKey) })); const guideLine: BookGuideLineTable[] = encryptedGuidelines.map((guide: BookGuideLineTable): BookGuideLineTable => ({ ...guide, tone: guide.tone ? System.decryptDataWithUserKey(guide.tone, userEncryptionKey) : null, atmosphere: guide.atmosphere ? System.decryptDataWithUserKey(guide.atmosphere, userEncryptionKey) : null, writing_style: guide.writing_style ? System.decryptDataWithUserKey(guide.writing_style, userEncryptionKey) : null, themes: guide.themes ? System.decryptDataWithUserKey(guide.themes, userEncryptionKey) : null, symbolism: guide.symbolism ? System.decryptDataWithUserKey(guide.symbolism, userEncryptionKey) : null, motifs: guide.motifs ? System.decryptDataWithUserKey(guide.motifs, userEncryptionKey) : null, narrative_voice: guide.narrative_voice ? System.decryptDataWithUserKey(guide.narrative_voice, userEncryptionKey) : null, pacing: guide.pacing ? System.decryptDataWithUserKey(guide.pacing, userEncryptionKey) : null, intended_audience: guide.intended_audience ? System.decryptDataWithUserKey(guide.intended_audience, userEncryptionKey) : null, key_messages: guide.key_messages ? System.decryptDataWithUserKey(guide.key_messages, userEncryptionKey) : null })); const incidents: BookIncidentsTable[] = encryptedIncidents.map((incident: BookIncidentsTable): BookIncidentsTable => ({ ...incident, title: System.decryptDataWithUserKey(incident.title, userEncryptionKey), summary: incident.summary ? System.decryptDataWithUserKey(incident.summary, userEncryptionKey) : null })); const issues: BookIssuesTable[] = encryptedIssues.map((issue: BookIssuesTable): BookIssuesTable => ({ ...issue, name: System.decryptDataWithUserKey(issue.name, userEncryptionKey) })); const locations: BookLocationTable[] = encryptedLocations.map((location: BookLocationTable): BookLocationTable => ({ ...location, loc_name: System.decryptDataWithUserKey(location.loc_name, userEncryptionKey) })); const plotPoints: BookPlotPointsTable[] = encryptedPlotPoints.map((plotPoint: BookPlotPointsTable): BookPlotPointsTable => ({ ...plotPoint, title: System.decryptDataWithUserKey(plotPoint.title, userEncryptionKey), summary: plotPoint.summary ? System.decryptDataWithUserKey(plotPoint.summary, userEncryptionKey) : null })); const worlds: BookWorldTable[] = encryptedWorlds.map((world: BookWorldTable): BookWorldTable => ({ ...world, name: System.decryptDataWithUserKey(world.name, userEncryptionKey), history: world.history ? System.decryptDataWithUserKey(world.history, userEncryptionKey) : null, politics: world.politics ? System.decryptDataWithUserKey(world.politics, userEncryptionKey) : null, economy: world.economy ? System.decryptDataWithUserKey(world.economy, userEncryptionKey) : null, religion: world.religion ? System.decryptDataWithUserKey(world.religion, userEncryptionKey) : null, languages: world.languages ? System.decryptDataWithUserKey(world.languages, userEncryptionKey) : null })); const worldElements: BookWorldElementsTable[] = encryptedWorldElements.map((worldElement: BookWorldElementsTable): BookWorldElementsTable => ({ ...worldElement, name: System.decryptDataWithUserKey(worldElement.name, userEncryptionKey), description: worldElement.description ? System.decryptDataWithUserKey(worldElement.description, userEncryptionKey) : null })); const locationElements: LocationElementTable[] = encryptedLocationElements.map((locationElement: LocationElementTable): LocationElementTable => ({ ...locationElement, element_name: System.decryptDataWithUserKey(locationElement.element_name, userEncryptionKey), element_description: locationElement.element_description ? System.decryptDataWithUserKey(locationElement.element_description, userEncryptionKey) : null })); const locationSubElements: LocationSubElementTable[] = encryptedLocationSubElements.map((locationSubElement: LocationSubElementTable): LocationSubElementTable => ({ ...locationSubElement, sub_elem_name: System.decryptDataWithUserKey(locationSubElement.sub_elem_name, userEncryptionKey), sub_elem_description: locationSubElement.sub_elem_description ? System.decryptDataWithUserKey(locationSubElement.sub_elem_description, userEncryptionKey) : null })); const bookTools: BookToolsTable[] = bookToolsData ? [bookToolsData] : []; const spells: BookSpellsTable[] = encryptedSpells.map((spell: BookSpellsTable): BookSpellsTable => ({ ...spell, name: System.decryptDataWithUserKey(spell.name, userEncryptionKey), description: System.decryptDataWithUserKey(spell.description, userEncryptionKey), appearance: System.decryptDataWithUserKey(spell.appearance, userEncryptionKey), tags: System.decryptDataWithUserKey(spell.tags, userEncryptionKey), power_level: spell.power_level ? System.decryptDataWithUserKey(spell.power_level, userEncryptionKey) : null, components: spell.components ? System.decryptDataWithUserKey(spell.components, userEncryptionKey) : null, limitations: spell.limitations ? System.decryptDataWithUserKey(spell.limitations, userEncryptionKey) : null, notes: spell.notes ? System.decryptDataWithUserKey(spell.notes, userEncryptionKey) : null })); const spellTags: BookSpellTagsTable[] = encryptedSpellTags.map((spellTag: BookSpellTagsTable): BookSpellTagsTable => ({ ...spellTag, name: System.decryptDataWithUserKey(spellTag.name, userEncryptionKey) })); return { eritBooks, actSummaries, aiGuideLine, chapters, chapterContents, chapterInfos, characters, characterAttributes, guideLine, incidents, issues, locations, plotPoints, worlds, worldElements, locationElements, locationSubElements, bookTools, spells, spellTags }; } }