- Deleted `CharacterComponent` and `CharacterDetail` files from the project. - Refactored related logic to improve code maintainability and reduce redundancy.
1024 lines
50 KiB
TypeScript
1024 lines
50 KiB
TypeScript
import { getUserEncryptionKey } from "../keyManager.js";
|
|
import System from "../System.js";
|
|
import SeriesSyncRepo, { SyncElementType } from "../repositories/series-sync.repo.js";
|
|
import Sync from "./Sync.js";
|
|
import {
|
|
CompleteSeries,
|
|
SyncedSeries,
|
|
SeriesTable,
|
|
SeriesBooksTable,
|
|
SeriesCharactersTable,
|
|
SeriesCharacterAttributesTable,
|
|
SeriesWorldsTable,
|
|
SeriesWorldElementsTable,
|
|
SeriesLocationsTable,
|
|
SeriesLocationElementsTable,
|
|
SeriesLocationSubElementsTable,
|
|
SeriesSpellsTable,
|
|
SeriesSpellTagsTable
|
|
} from "./Book.js";
|
|
import SeriesRepo from "../repositories/series.repo.js";
|
|
import BookRepo from "../repositories/book.repository.js";
|
|
import SeriesCharacterRepo from "../repositories/series-character.repo.js";
|
|
import SeriesWorldRepo from "../repositories/series-world.repo.js";
|
|
import SeriesLocationRepo from "../repositories/series-location.repo.js";
|
|
import SeriesSpellRepo from "../repositories/series-spell.repo.js";
|
|
|
|
export interface SeriesSyncUploadPayload {
|
|
type: SyncElementType;
|
|
bookElementId: string;
|
|
field: string;
|
|
value: string;
|
|
}
|
|
|
|
export interface SeriesSyncResult {
|
|
success: boolean;
|
|
updatedCount: number;
|
|
}
|
|
|
|
export type { CompleteSeries, SyncedSeries };
|
|
|
|
/**
|
|
* Handles series synchronization operations.
|
|
* Manages field propagation from book elements to series elements,
|
|
* and provides methods for complete series upload/download synchronization.
|
|
*/
|
|
export default class SeriesSync {
|
|
/**
|
|
* Uploads a field value from a book element to its linked series element,
|
|
* then propagates the change to all other book elements linked to the same series element.
|
|
* @param userId - The unique identifier of the user
|
|
* @param payload - Contains type, bookElementId, field, and value
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
* @returns Result containing success status and count of updated book elements
|
|
*/
|
|
static uploadFieldToSeries(userId: string, payload: SeriesSyncUploadPayload, lang: 'fr' | 'en'): SeriesSyncResult {
|
|
const { type, bookElementId, field, value } = payload;
|
|
|
|
// 1. Get the series element ID linked to the book element
|
|
const seriesElementId: string | null = this.getSeriesLink(userId, type, bookElementId, lang);
|
|
if (!seriesElementId) {
|
|
return { success: false, updatedCount: 0 };
|
|
}
|
|
|
|
// 2. Encrypt the value
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
const encryptedValue: string = System.encryptDataWithUserKey(value, userEncryptionKey);
|
|
|
|
// 3. Map the frontend field name to the database column name
|
|
const dbColumn: string = this.mapFieldToDbColumn(type, field);
|
|
|
|
// 4. Update the series element
|
|
const seriesUpdated: boolean = this.updateSeriesElement(userId, type, seriesElementId, dbColumn, encryptedValue, lang);
|
|
if (!seriesUpdated) {
|
|
return { success: false, updatedCount: 0 };
|
|
}
|
|
|
|
// 5. Map the series field to the book field (may be different for some types)
|
|
const bookField: string = this.mapSeriesFieldToBookField(type, dbColumn);
|
|
|
|
// 6. Update all linked book elements
|
|
const updatedCount: number = this.updateLinkedBookElements(userId, type, seriesElementId, bookField, encryptedValue, lang);
|
|
|
|
return { success: true, updatedCount };
|
|
}
|
|
|
|
/**
|
|
* Gets the series element ID linked to a book element.
|
|
*/
|
|
private static getSeriesLink(userId: string, type: SyncElementType, bookElementId: string, lang: 'fr' | 'en'): string | null {
|
|
switch (type) {
|
|
case 'character':
|
|
return SeriesSyncRepo.getCharacterSeriesLink(userId, bookElementId, lang);
|
|
case 'world':
|
|
return SeriesSyncRepo.getWorldSeriesLink(userId, bookElementId, lang);
|
|
case 'location':
|
|
return SeriesSyncRepo.getLocationSeriesLink(userId, bookElementId, lang);
|
|
case 'spell':
|
|
return SeriesSyncRepo.getSpellSeriesLink(userId, bookElementId, lang);
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maps frontend field names to database column names.
|
|
*/
|
|
private static mapFieldToDbColumn(type: SyncElementType, field: string): string {
|
|
// Most fields have the same name, but some need mapping
|
|
const fieldMappings: Record<string, Record<string, string>> = {
|
|
character: {
|
|
firstName: 'first_name',
|
|
lastName: 'last_name',
|
|
speechPattern: 'speech_pattern'
|
|
},
|
|
world: {},
|
|
location: {
|
|
name: 'name',
|
|
locName: 'loc_name'
|
|
},
|
|
spell: {
|
|
powerLevel: 'power_level'
|
|
}
|
|
};
|
|
|
|
const typeMapping = fieldMappings[type] || {};
|
|
return typeMapping[field] || field;
|
|
}
|
|
|
|
/**
|
|
* Updates a field in the series element.
|
|
*/
|
|
private static updateSeriesElement(
|
|
userId: string,
|
|
type: SyncElementType,
|
|
seriesElementId: string,
|
|
field: string,
|
|
encryptedValue: string,
|
|
lang: 'fr' | 'en'
|
|
): boolean {
|
|
switch (type) {
|
|
case 'character':
|
|
return SeriesSyncRepo.updateSeriesCharacterField(userId, seriesElementId, field, encryptedValue, lang);
|
|
case 'world':
|
|
return SeriesSyncRepo.updateSeriesWorldField(userId, seriesElementId, field, encryptedValue, lang);
|
|
case 'location':
|
|
return SeriesSyncRepo.updateSeriesLocationField(userId, seriesElementId, field, encryptedValue, lang);
|
|
case 'spell':
|
|
return SeriesSyncRepo.updateSeriesSpellField(userId, seriesElementId, field, encryptedValue, lang);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates all book elements linked to a series element.
|
|
*/
|
|
private static updateLinkedBookElements(
|
|
userId: string,
|
|
type: SyncElementType,
|
|
seriesElementId: string,
|
|
field: string,
|
|
encryptedValue: string,
|
|
lang: 'fr' | 'en'
|
|
): number {
|
|
switch (type) {
|
|
case 'character':
|
|
return SeriesSyncRepo.updateLinkedBookCharactersField(userId, seriesElementId, field, encryptedValue, lang);
|
|
case 'world':
|
|
return SeriesSyncRepo.updateLinkedBookWorldsField(userId, seriesElementId, field, encryptedValue, lang);
|
|
case 'location':
|
|
return SeriesSyncRepo.updateLinkedBookLocationsField(userId, seriesElementId, field, encryptedValue, lang);
|
|
case 'spell':
|
|
return SeriesSyncRepo.updateLinkedBookSpellsField(userId, seriesElementId, field, encryptedValue, lang);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maps series field names to book field names (they may differ).
|
|
*/
|
|
private static mapSeriesFieldToBookField(type: SyncElementType, seriesField: string): string {
|
|
const fieldMappings: Record<string, Record<string, string>> = {
|
|
location: {
|
|
name: 'loc_name'
|
|
}
|
|
};
|
|
|
|
const typeMapping = fieldMappings[type] || {};
|
|
return typeMapping[seriesField] || seriesField;
|
|
}
|
|
|
|
// ===== SYNC METHODS =====
|
|
|
|
/**
|
|
* Gets all synced series for a user.
|
|
* Delegates to Sync.getSyncedSeries which already implements this functionality.
|
|
*/
|
|
static getSyncedSeries(userId: string, lang: 'fr' | 'en'): SyncedSeries[] {
|
|
return Sync.getSyncedSeries(userId, lang);
|
|
}
|
|
|
|
/**
|
|
* Gets a complete series with all data decrypted for upload to server.
|
|
* @param userId - The unique identifier of the user
|
|
* @param seriesId - The unique identifier of the series
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
* @returns Complete series object with all decrypted data
|
|
*/
|
|
static getCompleteSeriesForUpload(userId: string, seriesId: string, lang: 'fr' | 'en'): CompleteSeries {
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
|
|
// Fetch all series data - use table fetch methods that return arrays
|
|
const seriesData = SeriesRepo.fetchSeriesTableForSync(userId, seriesId, lang);
|
|
const seriesBooksData = SeriesRepo.fetchSeriesBooksTable(seriesId, lang);
|
|
const charactersData = SeriesCharacterRepo.fetchSeriesCharactersTable(userId, seriesId, lang);
|
|
const characterAttributesData = SeriesCharacterRepo.fetchSeriesCharacterAttributesBySeriesId(userId, seriesId, lang);
|
|
const worldsData = SeriesWorldRepo.fetchSeriesWorldsTable(userId, seriesId, lang);
|
|
const worldElementsData = SeriesWorldRepo.fetchSeriesWorldElementsBySeriesId(userId, seriesId, lang);
|
|
const locationsData = SeriesLocationRepo.fetchSeriesLocationsTable(userId, seriesId, lang);
|
|
const locationElementsData = SeriesLocationRepo.fetchSeriesLocationElementsBySeriesId(userId, seriesId, lang);
|
|
const locationSubElementsData = SeriesLocationRepo.fetchSeriesLocationSubElementsBySeriesId(userId, seriesId, lang);
|
|
const spellsData = SeriesSpellRepo.fetchSeriesSpellsTable(userId, seriesId, lang);
|
|
const spellTagsData = SeriesSpellRepo.fetchSeriesSpellTagsTable(userId, seriesId, lang);
|
|
|
|
// Decrypt series
|
|
const series: SeriesTable[] = seriesData.map((s: SeriesTable): SeriesTable => ({
|
|
...s,
|
|
name: System.decryptDataWithUserKey(s.name, userEncryptionKey),
|
|
description: s.description ? System.decryptDataWithUserKey(s.description, userEncryptionKey) : null,
|
|
cover_image: s.cover_image ? System.decryptDataWithUserKey(s.cover_image, userEncryptionKey) : null
|
|
}));
|
|
|
|
// Decrypt characters
|
|
const seriesCharacters: SeriesCharactersTable[] = charactersData.map((c): SeriesCharactersTable => {
|
|
const decryptedAge: string | null = c.age ? System.decryptDataWithUserKey(c.age, userEncryptionKey) : null;
|
|
return {
|
|
character_id: c.character_id as string,
|
|
series_id: c.series_id as string,
|
|
user_id: c.user_id as string,
|
|
first_name: System.decryptDataWithUserKey(c.first_name as string, userEncryptionKey),
|
|
last_name: c.last_name ? System.decryptDataWithUserKey(c.last_name as string, userEncryptionKey) : null,
|
|
nickname: c.nickname ? System.decryptDataWithUserKey(c.nickname as string, userEncryptionKey) : null,
|
|
age: decryptedAge ? parseInt(decryptedAge, 10) : null,
|
|
gender: c.gender ? System.decryptDataWithUserKey(c.gender as string, userEncryptionKey) : null,
|
|
species: c.species ? System.decryptDataWithUserKey(c.species as string, userEncryptionKey) : null,
|
|
nationality: c.nationality ? System.decryptDataWithUserKey(c.nationality as string, userEncryptionKey) : null,
|
|
status: c.status ? System.decryptDataWithUserKey(c.status as string, userEncryptionKey) : null,
|
|
title: c.title ? System.decryptDataWithUserKey(c.title as string, userEncryptionKey) : null,
|
|
category: System.decryptDataWithUserKey(c.category as string, userEncryptionKey),
|
|
image: c.image ? System.decryptDataWithUserKey(c.image as string, userEncryptionKey) : null,
|
|
role: c.role ? System.decryptDataWithUserKey(c.role as string, userEncryptionKey) : null,
|
|
biography: c.biography ? System.decryptDataWithUserKey(c.biography as string, userEncryptionKey) : null,
|
|
history: c.history ? System.decryptDataWithUserKey(c.history as string, userEncryptionKey) : null,
|
|
speech_pattern: c.speech_pattern ? System.decryptDataWithUserKey(c.speech_pattern as string, userEncryptionKey) : null,
|
|
catchphrase: c.catchphrase ? System.decryptDataWithUserKey(c.catchphrase as string, userEncryptionKey) : null,
|
|
residence: c.residence ? System.decryptDataWithUserKey(c.residence as string, userEncryptionKey) : null,
|
|
notes: c.notes ? System.decryptDataWithUserKey(c.notes as string, userEncryptionKey) : null,
|
|
color: c.color ? System.decryptDataWithUserKey(c.color as string, userEncryptionKey) : null,
|
|
last_update: c.last_update as number
|
|
};
|
|
});
|
|
|
|
// Decrypt character attributes
|
|
const seriesCharacterAttributes: SeriesCharacterAttributesTable[] = characterAttributesData.map((a: SeriesCharacterAttributesTable): SeriesCharacterAttributesTable => ({
|
|
...a,
|
|
attribute_name: System.decryptDataWithUserKey(a.attribute_name, userEncryptionKey),
|
|
attribute_value: System.decryptDataWithUserKey(a.attribute_value, userEncryptionKey)
|
|
}));
|
|
|
|
// Decrypt worlds
|
|
const seriesWorlds: SeriesWorldsTable[] = worldsData.map((w: SeriesWorldsTable): SeriesWorldsTable => ({
|
|
...w,
|
|
name: System.decryptDataWithUserKey(w.name, userEncryptionKey),
|
|
history: w.history ? System.decryptDataWithUserKey(w.history, userEncryptionKey) : null,
|
|
politics: w.politics ? System.decryptDataWithUserKey(w.politics, userEncryptionKey) : null,
|
|
economy: w.economy ? System.decryptDataWithUserKey(w.economy, userEncryptionKey) : null,
|
|
religion: w.religion ? System.decryptDataWithUserKey(w.religion, userEncryptionKey) : null,
|
|
languages: w.languages ? System.decryptDataWithUserKey(w.languages, userEncryptionKey) : null
|
|
}));
|
|
|
|
// Decrypt world elements
|
|
const seriesWorldElements: SeriesWorldElementsTable[] = worldElementsData.map((e: SeriesWorldElementsTable): SeriesWorldElementsTable => ({
|
|
...e,
|
|
name: System.decryptDataWithUserKey(e.name, userEncryptionKey),
|
|
description: e.description ? System.decryptDataWithUserKey(e.description, userEncryptionKey) : null
|
|
}));
|
|
|
|
// Decrypt locations
|
|
const seriesLocations: SeriesLocationsTable[] = locationsData.map((l: SeriesLocationsTable): SeriesLocationsTable => ({
|
|
...l,
|
|
loc_name: System.decryptDataWithUserKey(l.loc_name, userEncryptionKey)
|
|
}));
|
|
|
|
// Decrypt location elements
|
|
const seriesLocationElements: SeriesLocationElementsTable[] = locationElementsData.map((e: SeriesLocationElementsTable): SeriesLocationElementsTable => ({
|
|
...e,
|
|
element_name: System.decryptDataWithUserKey(e.element_name, userEncryptionKey),
|
|
element_description: e.element_description ? System.decryptDataWithUserKey(e.element_description, userEncryptionKey) : null
|
|
}));
|
|
|
|
// Decrypt location sub-elements
|
|
const seriesLocationSubElements: SeriesLocationSubElementsTable[] = locationSubElementsData.map((se: SeriesLocationSubElementsTable): SeriesLocationSubElementsTable => ({
|
|
...se,
|
|
sub_elem_name: System.decryptDataWithUserKey(se.sub_elem_name, userEncryptionKey),
|
|
sub_elem_description: se.sub_elem_description ? System.decryptDataWithUserKey(se.sub_elem_description, userEncryptionKey) : null
|
|
}));
|
|
|
|
// Decrypt spells
|
|
const seriesSpells: SeriesSpellsTable[] = spellsData.map((s): SeriesSpellsTable => ({
|
|
spell_id: s.spell_id as string,
|
|
series_id: s.series_id as string,
|
|
user_id: s.user_id as string,
|
|
name: System.decryptDataWithUserKey(s.name as string, userEncryptionKey),
|
|
name_hash: s.name_hash as string,
|
|
description: s.description ? System.decryptDataWithUserKey(s.description as string, userEncryptionKey) : '',
|
|
appearance: s.appearance ? System.decryptDataWithUserKey(s.appearance as string, userEncryptionKey) : '',
|
|
tags: s.tags ? System.decryptDataWithUserKey(s.tags as string, userEncryptionKey) : '',
|
|
power_level: s.power_level ? System.decryptDataWithUserKey(s.power_level as string, userEncryptionKey) : null,
|
|
components: s.components ? System.decryptDataWithUserKey(s.components as string, userEncryptionKey) : null,
|
|
limitations: s.limitations ? System.decryptDataWithUserKey(s.limitations as string, userEncryptionKey) : null,
|
|
notes: s.notes ? System.decryptDataWithUserKey(s.notes as string, userEncryptionKey) : null,
|
|
last_update: s.last_update as number
|
|
}));
|
|
|
|
// Decrypt spell tags
|
|
const seriesSpellTags: SeriesSpellTagsTable[] = spellTagsData.map((t: SeriesSpellTagsTable): SeriesSpellTagsTable => ({
|
|
...t,
|
|
name: System.decryptDataWithUserKey(t.name, userEncryptionKey)
|
|
}));
|
|
|
|
return {
|
|
series,
|
|
seriesBooks: seriesBooksData,
|
|
seriesCharacters,
|
|
seriesCharacterAttributes,
|
|
seriesWorlds,
|
|
seriesWorldElements,
|
|
seriesLocations,
|
|
seriesLocationElements,
|
|
seriesLocationSubElements,
|
|
seriesSpells,
|
|
seriesSpellTags
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Saves a complete series downloaded from the server to the local database.
|
|
* Encrypts all data before storing.
|
|
* @param userId - The unique identifier of the user
|
|
* @param completeSeries - The complete series data from the server
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
* @returns True if save was successful, false otherwise
|
|
*/
|
|
static saveCompleteSeries(userId: string, completeSeries: CompleteSeries, lang: 'fr' | 'en'): boolean {
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
|
|
// Save series
|
|
for (const series of completeSeries.series) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(series.name, userEncryptionKey);
|
|
const encryptedDescription: string | null = series.description ? System.encryptDataWithUserKey(series.description, userEncryptionKey) : null;
|
|
const encryptedCoverImage: string | null = series.cover_image ? System.encryptDataWithUserKey(series.cover_image, userEncryptionKey) : null;
|
|
|
|
const success: boolean = SeriesRepo.insertSyncSeries(
|
|
series.series_id,
|
|
userId,
|
|
encryptedName,
|
|
series.hashed_name,
|
|
encryptedDescription,
|
|
encryptedCoverImage,
|
|
series.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Save series books (only if the book exists locally)
|
|
for (const seriesBook of completeSeries.seriesBooks) {
|
|
const bookExists: boolean = BookRepo.isBookExist(userId, seriesBook.book_id, lang);
|
|
if (!bookExists) continue;
|
|
|
|
const success: boolean = SeriesRepo.insertSyncSeriesBook(
|
|
seriesBook.series_id,
|
|
seriesBook.book_id,
|
|
seriesBook.book_order,
|
|
seriesBook.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Save characters
|
|
for (const character of completeSeries.seriesCharacters) {
|
|
const encFirstName: string = System.encryptDataWithUserKey(character.first_name, userEncryptionKey);
|
|
const encLastName: string | null = character.last_name ? System.encryptDataWithUserKey(character.last_name, userEncryptionKey) : null;
|
|
const encNickname: string | null = character.nickname ? System.encryptDataWithUserKey(character.nickname, userEncryptionKey) : null;
|
|
const encAge: string | null = character.age !== null ? System.encryptDataWithUserKey(String(character.age), userEncryptionKey) : null;
|
|
const encGender: string | null = character.gender ? System.encryptDataWithUserKey(character.gender, userEncryptionKey) : null;
|
|
const encSpecies: string | null = character.species ? System.encryptDataWithUserKey(character.species, userEncryptionKey) : null;
|
|
const encNationality: string | null = character.nationality ? System.encryptDataWithUserKey(character.nationality, userEncryptionKey) : null;
|
|
const encStatus: string | null = character.status ? System.encryptDataWithUserKey(character.status, userEncryptionKey) : null;
|
|
const encTitle: string | null = character.title ? System.encryptDataWithUserKey(character.title, userEncryptionKey) : null;
|
|
const encCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey);
|
|
const encImage: string | null = character.image ? System.encryptDataWithUserKey(character.image, userEncryptionKey) : null;
|
|
const encRole: string | null = character.role ? System.encryptDataWithUserKey(character.role, userEncryptionKey) : null;
|
|
const encBiography: string | null = character.biography ? System.encryptDataWithUserKey(character.biography, userEncryptionKey) : null;
|
|
const encHistory: string | null = character.history ? System.encryptDataWithUserKey(character.history, userEncryptionKey) : null;
|
|
const encSpeechPattern: string | null = character.speech_pattern ? System.encryptDataWithUserKey(character.speech_pattern, userEncryptionKey) : null;
|
|
const encCatchphrase: string | null = character.catchphrase ? System.encryptDataWithUserKey(character.catchphrase, userEncryptionKey) : null;
|
|
const encResidence: string | null = character.residence ? System.encryptDataWithUserKey(character.residence, userEncryptionKey) : null;
|
|
const encNotes: string | null = character.notes ? System.encryptDataWithUserKey(character.notes, userEncryptionKey) : null;
|
|
const encColor: string | null = character.color ? System.encryptDataWithUserKey(character.color, userEncryptionKey) : null;
|
|
|
|
const success: boolean = SeriesCharacterRepo.insertSyncSeriesCharacter(
|
|
character.character_id,
|
|
character.series_id,
|
|
userId,
|
|
encFirstName,
|
|
encLastName,
|
|
encNickname,
|
|
encAge,
|
|
encGender,
|
|
encSpecies,
|
|
encNationality,
|
|
encStatus,
|
|
encCategory,
|
|
encTitle,
|
|
encImage,
|
|
encRole,
|
|
encBiography,
|
|
encHistory,
|
|
encSpeechPattern,
|
|
encCatchphrase,
|
|
encResidence,
|
|
encNotes,
|
|
encColor,
|
|
character.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Save character attributes
|
|
for (const attr of completeSeries.seriesCharacterAttributes) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(attr.attribute_name, userEncryptionKey);
|
|
const encryptedValue: string = System.encryptDataWithUserKey(attr.attribute_value, userEncryptionKey);
|
|
|
|
const success: boolean = SeriesCharacterRepo.insertSyncSeriesCharacterAttribute(
|
|
attr.attr_id,
|
|
attr.character_id,
|
|
userId,
|
|
encryptedName,
|
|
encryptedValue,
|
|
attr.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Save worlds
|
|
for (const world of completeSeries.seriesWorlds) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(world.name, userEncryptionKey);
|
|
const encryptedHistory: string | null = world.history ? System.encryptDataWithUserKey(world.history, userEncryptionKey) : null;
|
|
const encryptedPolitics: string | null = world.politics ? System.encryptDataWithUserKey(world.politics, userEncryptionKey) : null;
|
|
const encryptedEconomy: string | null = world.economy ? System.encryptDataWithUserKey(world.economy, userEncryptionKey) : null;
|
|
const encryptedReligion: string | null = world.religion ? System.encryptDataWithUserKey(world.religion, userEncryptionKey) : null;
|
|
const encryptedLanguages: string | null = world.languages ? System.encryptDataWithUserKey(world.languages, userEncryptionKey) : null;
|
|
|
|
const success: boolean = SeriesWorldRepo.insertSyncSeriesWorld(
|
|
world.world_id,
|
|
world.series_id,
|
|
userId,
|
|
encryptedName,
|
|
world.hashed_name,
|
|
encryptedHistory,
|
|
encryptedPolitics,
|
|
encryptedEconomy,
|
|
encryptedReligion,
|
|
encryptedLanguages,
|
|
world.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Save world elements
|
|
for (const element of completeSeries.seriesWorldElements) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(element.name, userEncryptionKey);
|
|
const encryptedDescription: string | null = element.description ? System.encryptDataWithUserKey(element.description, userEncryptionKey) : null;
|
|
|
|
const success: boolean = SeriesWorldRepo.insertSyncSeriesWorldElement(
|
|
element.element_id,
|
|
element.world_id,
|
|
userId,
|
|
element.element_type,
|
|
encryptedName,
|
|
element.original_name,
|
|
encryptedDescription,
|
|
element.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Save locations
|
|
for (const location of completeSeries.seriesLocations) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(location.loc_name, userEncryptionKey);
|
|
|
|
const success: boolean = SeriesLocationRepo.insertSyncSeriesLocation(
|
|
location.loc_id,
|
|
location.series_id,
|
|
userId,
|
|
encryptedName,
|
|
location.loc_original_name,
|
|
location.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Save location elements
|
|
for (const element of completeSeries.seriesLocationElements) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(element.element_name, userEncryptionKey);
|
|
const encryptedDescription: string | null = element.element_description ? System.encryptDataWithUserKey(element.element_description, userEncryptionKey) : null;
|
|
|
|
const success: boolean = SeriesLocationRepo.insertSyncSeriesLocationElement(
|
|
element.element_id,
|
|
element.location_id,
|
|
userId,
|
|
encryptedName,
|
|
element.original_name,
|
|
encryptedDescription,
|
|
element.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Save location sub-elements
|
|
for (const subElement of completeSeries.seriesLocationSubElements) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(subElement.sub_elem_name, userEncryptionKey);
|
|
const encryptedDescription: string | null = subElement.sub_elem_description ? System.encryptDataWithUserKey(subElement.sub_elem_description, userEncryptionKey) : null;
|
|
|
|
const success: boolean = SeriesLocationRepo.insertSyncSeriesLocationSubElement(
|
|
subElement.sub_element_id,
|
|
subElement.element_id,
|
|
userId,
|
|
encryptedName,
|
|
subElement.original_name,
|
|
encryptedDescription,
|
|
subElement.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Save spells
|
|
for (const spell of completeSeries.seriesSpells) {
|
|
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;
|
|
|
|
const success: boolean = SeriesSpellRepo.insertSyncSeriesSpell(
|
|
spell.spell_id,
|
|
spell.series_id,
|
|
userId,
|
|
encryptedName,
|
|
spell.name_hash,
|
|
encryptedDescription,
|
|
encryptedAppearance,
|
|
encryptedTags,
|
|
encryptedPowerLevel,
|
|
encryptedComponents,
|
|
encryptedLimitations,
|
|
encryptedNotes,
|
|
spell.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Save spell tags
|
|
for (const tag of completeSeries.seriesSpellTags) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(tag.name, userEncryptionKey);
|
|
|
|
const success: boolean = SeriesSpellRepo.insertSyncSeriesSpellTag(
|
|
tag.tag_id,
|
|
tag.series_id,
|
|
userId,
|
|
encryptedName,
|
|
tag.hashed_name,
|
|
tag.color,
|
|
tag.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Synchronizes a series from server to client, updating existing records or inserting new ones.
|
|
* @param userId - The unique identifier of the user
|
|
* @param completeSeries - The complete series data from the server
|
|
* @param lang - The language for error messages ('fr' or 'en')
|
|
* @returns True if sync was successful, false otherwise
|
|
*/
|
|
static syncSeriesFromServerToClient(userId: string, completeSeries: CompleteSeries, lang: 'fr' | 'en'): boolean {
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
|
|
// Sync series
|
|
for (const series of completeSeries.series) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(series.name, userEncryptionKey);
|
|
const encryptedDescription: string | null = series.description ? System.encryptDataWithUserKey(series.description, userEncryptionKey) : null;
|
|
const encryptedCoverImage: string | null = series.cover_image ? System.encryptDataWithUserKey(series.cover_image, userEncryptionKey) : null;
|
|
|
|
const exists: boolean = SeriesRepo.seriesExists(userId, series.series_id, lang);
|
|
if (exists) {
|
|
const success: boolean = SeriesRepo.updateSyncSeries(
|
|
userId,
|
|
series.series_id,
|
|
encryptedName,
|
|
series.hashed_name,
|
|
encryptedDescription,
|
|
encryptedCoverImage,
|
|
series.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
} else {
|
|
const success: boolean = SeriesRepo.insertSyncSeries(
|
|
series.series_id,
|
|
userId,
|
|
encryptedName,
|
|
series.hashed_name,
|
|
encryptedDescription,
|
|
encryptedCoverImage,
|
|
series.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
}
|
|
|
|
// Sync series books (only if the book exists locally)
|
|
for (const seriesBook of completeSeries.seriesBooks) {
|
|
const bookExists: boolean = BookRepo.isBookExist(userId, seriesBook.book_id, lang);
|
|
if (!bookExists) continue;
|
|
|
|
const success: boolean = SeriesRepo.insertSyncSeriesBook(
|
|
seriesBook.series_id,
|
|
seriesBook.book_id,
|
|
seriesBook.book_order,
|
|
seriesBook.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
|
|
// Sync characters
|
|
for (const character of completeSeries.seriesCharacters) {
|
|
const encFirstName: string = System.encryptDataWithUserKey(character.first_name, userEncryptionKey);
|
|
const encLastName: string | null = character.last_name ? System.encryptDataWithUserKey(character.last_name, userEncryptionKey) : null;
|
|
const encNickname: string | null = character.nickname ? System.encryptDataWithUserKey(character.nickname, userEncryptionKey) : null;
|
|
const encAge: string | null = character.age !== null ? System.encryptDataWithUserKey(String(character.age), userEncryptionKey) : null;
|
|
const encGender: string | null = character.gender ? System.encryptDataWithUserKey(character.gender, userEncryptionKey) : null;
|
|
const encSpecies: string | null = character.species ? System.encryptDataWithUserKey(character.species, userEncryptionKey) : null;
|
|
const encNationality: string | null = character.nationality ? System.encryptDataWithUserKey(character.nationality, userEncryptionKey) : null;
|
|
const encStatus: string | null = character.status ? System.encryptDataWithUserKey(character.status, userEncryptionKey) : null;
|
|
const encTitle: string | null = character.title ? System.encryptDataWithUserKey(character.title, userEncryptionKey) : null;
|
|
const encCategory: string = System.encryptDataWithUserKey(character.category, userEncryptionKey);
|
|
const encImage: string | null = character.image ? System.encryptDataWithUserKey(character.image, userEncryptionKey) : null;
|
|
const encRole: string | null = character.role ? System.encryptDataWithUserKey(character.role, userEncryptionKey) : null;
|
|
const encBiography: string | null = character.biography ? System.encryptDataWithUserKey(character.biography, userEncryptionKey) : null;
|
|
const encHistory: string | null = character.history ? System.encryptDataWithUserKey(character.history, userEncryptionKey) : null;
|
|
const encSpeechPattern: string | null = character.speech_pattern ? System.encryptDataWithUserKey(character.speech_pattern, userEncryptionKey) : null;
|
|
const encCatchphrase: string | null = character.catchphrase ? System.encryptDataWithUserKey(character.catchphrase, userEncryptionKey) : null;
|
|
const encResidence: string | null = character.residence ? System.encryptDataWithUserKey(character.residence, userEncryptionKey) : null;
|
|
const encNotes: string | null = character.notes ? System.encryptDataWithUserKey(character.notes, userEncryptionKey) : null;
|
|
const encColor: string | null = character.color ? System.encryptDataWithUserKey(character.color, userEncryptionKey) : null;
|
|
|
|
const exists: boolean = SeriesCharacterRepo.seriesCharacterExists(userId, character.character_id, lang);
|
|
if (exists) {
|
|
const success: boolean = SeriesCharacterRepo.updateSyncSeriesCharacter(
|
|
userId,
|
|
character.character_id,
|
|
encFirstName,
|
|
encLastName,
|
|
encNickname,
|
|
encAge,
|
|
encGender,
|
|
encSpecies,
|
|
encNationality,
|
|
encStatus,
|
|
encCategory,
|
|
encTitle,
|
|
encImage,
|
|
encRole,
|
|
encBiography,
|
|
encHistory,
|
|
encSpeechPattern,
|
|
encCatchphrase,
|
|
encResidence,
|
|
encNotes,
|
|
encColor,
|
|
character.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
} else {
|
|
const success: boolean = SeriesCharacterRepo.insertSyncSeriesCharacter(
|
|
character.character_id,
|
|
character.series_id,
|
|
userId,
|
|
encFirstName,
|
|
encLastName,
|
|
encNickname,
|
|
encAge,
|
|
encGender,
|
|
encSpecies,
|
|
encNationality,
|
|
encStatus,
|
|
encCategory,
|
|
encTitle,
|
|
encImage,
|
|
encRole,
|
|
encBiography,
|
|
encHistory,
|
|
encSpeechPattern,
|
|
encCatchphrase,
|
|
encResidence,
|
|
encNotes,
|
|
encColor,
|
|
character.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
}
|
|
|
|
// Sync character attributes
|
|
for (const attr of completeSeries.seriesCharacterAttributes) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(attr.attribute_name, userEncryptionKey);
|
|
const encryptedValue: string = System.encryptDataWithUserKey(attr.attribute_value, userEncryptionKey);
|
|
|
|
const exists: boolean = SeriesCharacterRepo.seriesCharacterAttributeExists(userId, attr.attr_id, lang);
|
|
if (exists) {
|
|
const success: boolean = SeriesCharacterRepo.updateSyncSeriesCharacterAttribute(
|
|
userId,
|
|
attr.attr_id,
|
|
encryptedName,
|
|
encryptedValue,
|
|
attr.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
} else {
|
|
const success: boolean = SeriesCharacterRepo.insertSyncSeriesCharacterAttribute(
|
|
attr.attr_id,
|
|
attr.character_id,
|
|
userId,
|
|
encryptedName,
|
|
encryptedValue,
|
|
attr.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
}
|
|
|
|
// Sync worlds
|
|
for (const world of completeSeries.seriesWorlds) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(world.name, userEncryptionKey);
|
|
const encryptedHistory: string | null = world.history ? System.encryptDataWithUserKey(world.history, userEncryptionKey) : null;
|
|
const encryptedPolitics: string | null = world.politics ? System.encryptDataWithUserKey(world.politics, userEncryptionKey) : null;
|
|
const encryptedEconomy: string | null = world.economy ? System.encryptDataWithUserKey(world.economy, userEncryptionKey) : null;
|
|
const encryptedReligion: string | null = world.religion ? System.encryptDataWithUserKey(world.religion, userEncryptionKey) : null;
|
|
const encryptedLanguages: string | null = world.languages ? System.encryptDataWithUserKey(world.languages, userEncryptionKey) : null;
|
|
|
|
const exists: boolean = SeriesWorldRepo.seriesWorldExists(userId, world.world_id, lang);
|
|
if (exists) {
|
|
const success: boolean = SeriesWorldRepo.updateSyncSeriesWorld(
|
|
world.world_id,
|
|
userId,
|
|
encryptedName,
|
|
encryptedHistory,
|
|
encryptedPolitics,
|
|
encryptedEconomy,
|
|
encryptedReligion,
|
|
encryptedLanguages,
|
|
world.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
} else {
|
|
const success: boolean = SeriesWorldRepo.insertSyncSeriesWorld(
|
|
world.world_id,
|
|
world.series_id,
|
|
userId,
|
|
encryptedName,
|
|
world.hashed_name,
|
|
encryptedHistory,
|
|
encryptedPolitics,
|
|
encryptedEconomy,
|
|
encryptedReligion,
|
|
encryptedLanguages,
|
|
world.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
}
|
|
|
|
// Sync world elements
|
|
for (const element of completeSeries.seriesWorldElements) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(element.name, userEncryptionKey);
|
|
const encryptedDescription: string | null = element.description ? System.encryptDataWithUserKey(element.description, userEncryptionKey) : null;
|
|
|
|
const exists: boolean = SeriesWorldRepo.seriesWorldElementExists(userId, element.element_id, lang);
|
|
if (exists) {
|
|
const success: boolean = SeriesWorldRepo.updateSyncSeriesWorldElement(
|
|
element.element_id,
|
|
userId,
|
|
encryptedName,
|
|
encryptedDescription,
|
|
element.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
} else {
|
|
const success: boolean = SeriesWorldRepo.insertSyncSeriesWorldElement(
|
|
element.element_id,
|
|
element.world_id,
|
|
userId,
|
|
element.element_type,
|
|
encryptedName,
|
|
element.original_name,
|
|
encryptedDescription,
|
|
element.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
}
|
|
|
|
// Sync locations
|
|
for (const location of completeSeries.seriesLocations) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(location.loc_name, userEncryptionKey);
|
|
|
|
const exists: boolean = SeriesLocationRepo.seriesLocationExists(userId, location.loc_id, lang);
|
|
if (exists) {
|
|
const success: boolean = SeriesLocationRepo.updateSyncSeriesLocation(
|
|
location.loc_id,
|
|
userId,
|
|
encryptedName,
|
|
location.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
} else {
|
|
const success: boolean = SeriesLocationRepo.insertSyncSeriesLocation(
|
|
location.loc_id,
|
|
location.series_id,
|
|
userId,
|
|
encryptedName,
|
|
location.loc_original_name,
|
|
location.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
}
|
|
|
|
// Sync location elements
|
|
for (const element of completeSeries.seriesLocationElements) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(element.element_name, userEncryptionKey);
|
|
const encryptedDescription: string | null = element.element_description ? System.encryptDataWithUserKey(element.element_description, userEncryptionKey) : null;
|
|
|
|
const exists: boolean = SeriesLocationRepo.seriesLocationElementExists(userId, element.element_id, lang);
|
|
if (exists) {
|
|
const success: boolean = SeriesLocationRepo.updateSyncSeriesLocationElement(
|
|
element.element_id,
|
|
userId,
|
|
encryptedName,
|
|
encryptedDescription,
|
|
element.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
} else {
|
|
const success: boolean = SeriesLocationRepo.insertSyncSeriesLocationElement(
|
|
element.element_id,
|
|
element.location_id,
|
|
userId,
|
|
encryptedName,
|
|
element.original_name,
|
|
encryptedDescription,
|
|
element.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
}
|
|
|
|
// Sync location sub-elements
|
|
for (const subElement of completeSeries.seriesLocationSubElements) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(subElement.sub_elem_name, userEncryptionKey);
|
|
const encryptedDescription: string | null = subElement.sub_elem_description ? System.encryptDataWithUserKey(subElement.sub_elem_description, userEncryptionKey) : null;
|
|
|
|
const exists: boolean = SeriesLocationRepo.seriesLocationSubElementExists(userId, subElement.sub_element_id, lang);
|
|
if (exists) {
|
|
const success: boolean = SeriesLocationRepo.updateSyncSeriesLocationSubElement(
|
|
subElement.sub_element_id,
|
|
userId,
|
|
encryptedName,
|
|
encryptedDescription,
|
|
subElement.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
} else {
|
|
const success: boolean = SeriesLocationRepo.insertSyncSeriesLocationSubElement(
|
|
subElement.sub_element_id,
|
|
subElement.element_id,
|
|
userId,
|
|
encryptedName,
|
|
subElement.original_name,
|
|
encryptedDescription,
|
|
subElement.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
}
|
|
|
|
// Sync spells
|
|
for (const spell of completeSeries.seriesSpells) {
|
|
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;
|
|
|
|
const exists: boolean = SeriesSpellRepo.seriesSpellExists(userId, spell.spell_id, lang);
|
|
if (exists) {
|
|
const success: boolean = SeriesSpellRepo.updateSyncSeriesSpell(
|
|
spell.spell_id,
|
|
userId,
|
|
encryptedName,
|
|
encryptedDescription,
|
|
encryptedAppearance,
|
|
encryptedTags,
|
|
encryptedPowerLevel,
|
|
encryptedComponents,
|
|
encryptedLimitations,
|
|
encryptedNotes,
|
|
spell.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
} else {
|
|
const success: boolean = SeriesSpellRepo.insertSyncSeriesSpell(
|
|
spell.spell_id,
|
|
spell.series_id,
|
|
userId,
|
|
encryptedName,
|
|
spell.name_hash,
|
|
encryptedDescription,
|
|
encryptedAppearance,
|
|
encryptedTags,
|
|
encryptedPowerLevel,
|
|
encryptedComponents,
|
|
encryptedLimitations,
|
|
encryptedNotes,
|
|
spell.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
}
|
|
|
|
// Sync spell tags
|
|
for (const tag of completeSeries.seriesSpellTags) {
|
|
const encryptedName: string = System.encryptDataWithUserKey(tag.name, userEncryptionKey);
|
|
|
|
const exists: boolean = SeriesSpellRepo.seriesSpellTagExists(userId, tag.tag_id, lang);
|
|
if (exists) {
|
|
const success: boolean = SeriesSpellRepo.updateSyncSeriesSpellTag(
|
|
tag.tag_id,
|
|
userId,
|
|
encryptedName,
|
|
tag.color,
|
|
tag.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
} else {
|
|
const success: boolean = SeriesSpellRepo.insertSyncSeriesSpellTag(
|
|
tag.tag_id,
|
|
tag.series_id,
|
|
userId,
|
|
encryptedName,
|
|
tag.hashed_name,
|
|
tag.color,
|
|
tag.last_update,
|
|
lang
|
|
);
|
|
if (!success) return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|