Add BooksSyncContext, refine database schema, and enhance synchronization support

- Introduce `BooksSyncContext` for managing book synchronization states (server-only, local-only, to-sync, etc.).
- Remove `UserContext` and related dependencies.
- Refine localization strings (`en.json`) with sync-related updates (e.g., "toSyncFromServer", "toSyncToServer").
- Extend database schema with additional tables and fields for syncing books, chapters, and related entities.
- Add `last_update` fields and update corresponding repository methods to support synchronization logic.
- Enhance IPC handlers with stricter typing, data validation, and sync-aware operations.
This commit is contained in:
natreex
2025-12-07 14:36:03 -05:00
parent db2c88a42d
commit bb331b5c22
22 changed files with 2594 additions and 370 deletions

View File

@@ -1,21 +1,45 @@
import BookRepository, {
BookCoverQuery,
BookQuery,
ChapterBookResult, GuideLineAIQuery,
GuideLineQuery, WorldElementValue
} from '../repositories/book.repository.js';
import type {
IssueQuery,
ActQuery,
PlotPointQuery,
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 {getUserEncryptionKey} from '../keyManager.js';
import path from "path";
import fs from "fs";
import BookRepo from "../repositories/book.repository.js";
import Chapter, {ActChapter, ChapterContentData, ChapterProps} from "./Chapter.js";
import UserRepo from "../repositories/user.repository.js";
import ChapterRepo from "../repositories/chapter.repository.js";
@@ -35,6 +59,139 @@ export interface BookProps{
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 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;
@@ -928,4 +1085,385 @@ export default class Book {
this.cover = '';
}
}
static async getSyncedBooks(userId: string, lang: 'fr' | 'en'):Promise<SyncedBook[]> {
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 saveCompleteBook(userId: string, data: CompleteBook, lang: "fr" | "en"):Promise<boolean> {
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);
});
}
}