Files
ERitors-Scribe-Desktop/electron/database/models/SeriesSync.ts
natreex cec5830360 Introduce series management functionality and repository updates
- Added `series-world.repo.ts` to handle database operations related to series worlds and their elements.
- Implemented `series-sync.repo.ts` for managing synchronization between books and series.
- Expanded `spell.ipc.ts` data models to include `seriesSpellId` for spell synchronization.
- Refactored `insertSpellTag` method in `spelltag.repo.ts` for improved error handling and logic clarity.
2026-01-26 19:57:56 -05:00

200 lines
8.5 KiB
TypeScript

import { getUserEncryptionKey } from "../keyManager.js";
import System from "../System.js";
import SeriesSyncRepo, { SyncElementType } from "../repositories/series-sync.repo.js";
export interface SeriesSyncUploadPayload {
type: SyncElementType;
bookElementId: string;
field: string;
value: string;
}
export interface SeriesSyncResult {
success: boolean;
updatedCount: number;
}
export default class SeriesSync {
/**
* Uploads a field value from a book element to its linked series element,
* and propagates the change to all other book elements linked to the same series element.
* @param userId - The unique identifier of the user
* @param payload - The upload payload containing type, bookElementId, field, and value
* @param lang - The language for error messages ('fr' or 'en')
* @returns The upload response
*/
public static uploadFieldToSeries(userId: string, payload: SeriesSyncUploadPayload, lang: 'fr' | 'en' = 'fr'): SeriesSyncResult {
const { type, bookElementId, field, value } = payload;
const seriesElementId: string | null = this.getSeriesLink(userId, type, bookElementId, lang);
if (!seriesElementId) {
throw new Error(lang === 'fr' ? `Cet élément n'est pas lié à une série.` : `This element is not linked to a series.`);
}
const userKey: string = getUserEncryptionKey(userId);
const encryptedValue: string = value ? System.encryptDataWithUserKey(value, userKey) : '';
const dbField: string = this.mapFieldToDbColumn(type, field);
this.updateSeriesElement(userId, type, seriesElementId, dbField, encryptedValue, lang);
const updatedCount: number = this.updateLinkedBookElements(userId, type, seriesElementId, dbField, encryptedValue, lang);
return {
success: true,
updatedCount: updatedCount + 1
};
}
/**
* Gets the series element ID linked to a book element.
* @param userId - The unique identifier of the user
* @param type - The type of element (character, world, location, spell)
* @param bookElementId - The unique identifier of the book element
* @param lang - The language for error messages ('fr' or 'en')
* @returns The series element ID or null if not linked
*/
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);
}
}
/**
* Maps frontend field names to database column names.
* @param type - The type of element (character, world, location, spell)
* @param field - The frontend field name to map
* @returns The corresponding database column name
*/
private static mapFieldToDbColumn(type: SyncElementType, field: string): string {
const characterFieldMap: Record<string, string> = {
'name': 'first_name',
'firstName': 'first_name',
'lastName': 'last_name',
'nickname': 'nickname',
'age': 'age',
'gender': 'gender',
'species': 'species',
'nationality': 'nationality',
'status': 'status',
'title': 'title',
'category': 'category',
'role': 'role',
'biography': 'biography',
'history': 'history',
'speechPattern': 'speech_pattern',
'catchphrase': 'catchphrase',
'residence': 'residence',
'notes': 'notes',
'color': 'color'
};
const worldFieldMap: Record<string, string> = {
'name': 'name',
'history': 'history',
'politics': 'politics',
'economy': 'economy',
'religion': 'religion',
'languages': 'languages'
};
const locationFieldMap: Record<string, string> = {
'name': 'name',
'loc_name': 'loc_name'
};
const spellFieldMap: Record<string, string> = {
'name': 'name',
'description': 'description',
'type': 'type',
'level': 'level',
'range': 'range',
'duration': 'duration',
'cost': 'cost',
'effect': 'effect',
'components': 'components',
'notes': 'notes'
};
switch (type) {
case 'character':
return characterFieldMap[field] || field;
case 'world':
return worldFieldMap[field] || field;
case 'location':
return locationFieldMap[field] || field;
case 'spell':
return spellFieldMap[field] || field;
}
}
/**
* Updates the series element field.
* @param userId - The unique identifier of the user
* @param type - The type of element (character, world, location, spell)
* @param seriesElementId - The unique identifier of the series element
* @param field - The database column name to update
* @param encryptedValue - The encrypted value to set
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if updated successfully
*/
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);
}
}
/**
* Updates all book elements linked to the series element.
* @param userId - The unique identifier of the user
* @param type - The type of element (character, world, location, spell)
* @param seriesElementId - The unique identifier of the series element
* @param field - The database column name to update
* @param encryptedValue - The encrypted value to set
* @param lang - The language for error messages ('fr' or 'en')
* @returns The number of book elements updated
*/
private static updateLinkedBookElements(userId: string, type: SyncElementType, seriesElementId: string, field: string, encryptedValue: string, lang: 'fr' | 'en'): number {
const bookField: string = this.mapSeriesFieldToBookField(type, field);
switch (type) {
case 'character':
return SeriesSyncRepo.updateLinkedBookCharactersField(userId, seriesElementId, bookField, encryptedValue, lang);
case 'world':
return SeriesSyncRepo.updateLinkedBookWorldsField(userId, seriesElementId, bookField, encryptedValue, lang);
case 'location':
return SeriesSyncRepo.updateLinkedBookLocationsField(userId, seriesElementId, bookField, encryptedValue, lang);
case 'spell':
return SeriesSyncRepo.updateLinkedBookSpellsField(userId, seriesElementId, bookField, encryptedValue, lang);
}
}
/**
* Maps series table field names to book table field names (if different).
* @param type - The type of element (character, world, location, spell)
* @param seriesField - The series table field name
* @returns The corresponding book table field name
*/
private static mapSeriesFieldToBookField(type: SyncElementType, seriesField: string): string {
if (type === 'location') {
if (seriesField === 'name') {
return 'loc_name';
}
}
return seriesField;
}
}