Files
ERitors-Scribe-Desktop/electron/database/models/Download.ts
natreex cf6fb97bf0 Add models for guidelines, incidents, plot points, issues, acts, and world data
- Introduced new models: `GuideLine`, `Incident`, `PlotPoint`, `Issue`, `Act`, and `World` for managing book-related entities.
- Integrated encryption/decryption for sensitive properties in all models using user-specific keys.
- Added methods for CRUD operations and synchronization workflows with error handling and multilingual support.
- Improved maintainability with JSDoc comments and streamlined queries.
2026-01-12 13:38:10 -05:00

201 lines
18 KiB
TypeScript

import {getUserEncryptionKey} from "../keyManager.js";
import System from "../System.js";
import {CompleteBook} from "./Book.js";
import BookRepo, {EritBooksTable} 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<boolean> {
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;
return 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);
});
}
}