- 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.
281 lines
14 KiB
TypeScript
281 lines
14 KiB
TypeScript
import { getUserEncryptionKey } from "../keyManager.js";
|
|
import System from "../System.js";
|
|
import WorldRepository, { WorldElementValue, WorldQuery } from "../repositories/world.repository.js";
|
|
import BookRepo, {BookToolsTable} from "../repositories/book.repository.js";
|
|
|
|
export interface SyncedWorld {
|
|
id: string;
|
|
name: string;
|
|
lastUpdate: number;
|
|
elements: SyncedWorldElement[];
|
|
}
|
|
|
|
export interface SyncedWorldElement {
|
|
id: string;
|
|
name: string;
|
|
lastUpdate: number;
|
|
}
|
|
|
|
export interface WorldElement {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
type?: number;
|
|
}
|
|
|
|
export interface WorldProps {
|
|
id: string;
|
|
name: string;
|
|
history: string;
|
|
politics: string;
|
|
economy: string;
|
|
religion: string;
|
|
languages: string;
|
|
laws: WorldElement[];
|
|
biomes: WorldElement[];
|
|
issues: WorldElement[];
|
|
customs: WorldElement[];
|
|
kingdoms: WorldElement[];
|
|
climate: WorldElement[];
|
|
resources: WorldElement[];
|
|
wildlife: WorldElement[];
|
|
arts: WorldElement[];
|
|
ethnicGroups: WorldElement[];
|
|
socialClasses: WorldElement[];
|
|
importantCharacters: WorldElement[];
|
|
seriesWorldId?: string | null;
|
|
}
|
|
|
|
export interface WorldListResponse {
|
|
worlds: WorldProps[];
|
|
enabled: boolean;
|
|
}
|
|
|
|
/**
|
|
* Mapping of element type keys to their corresponding numeric type identifiers.
|
|
*/
|
|
const ELEMENT_TYPE_MAP: Record<string, number> = {
|
|
laws: 1,
|
|
biomes: 2,
|
|
issues: 3,
|
|
customs: 4,
|
|
kingdoms: 5,
|
|
climate: 6,
|
|
resources: 7,
|
|
wildlife: 8,
|
|
arts: 9,
|
|
ethnicGroups: 10,
|
|
socialClasses: 11,
|
|
importantCharacters: 12
|
|
};
|
|
|
|
/**
|
|
* Mapping of numeric type identifiers to their corresponding WorldProps keys.
|
|
*/
|
|
const ELEMENT_TYPE_KEYS: Record<number, keyof WorldProps> = {
|
|
1: 'laws',
|
|
2: 'biomes',
|
|
3: 'issues',
|
|
4: 'customs',
|
|
5: 'kingdoms',
|
|
6: 'climate',
|
|
7: 'resources',
|
|
8: 'wildlife',
|
|
9: 'arts',
|
|
10: 'ethnicGroups',
|
|
11: 'socialClasses',
|
|
12: 'importantCharacters'
|
|
};
|
|
|
|
export default class World {
|
|
/**
|
|
* Creates a new world for a book.
|
|
* @param userId - The unique identifier of the user creating the world
|
|
* @param bookId - The unique identifier of the book to associate the world with
|
|
* @param worldName - The name of the new world
|
|
* @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
|
|
* @param existingWorldId - Optional existing world ID for syncing purposes
|
|
* @returns The unique identifier of the newly created world
|
|
* @throws Error if a world with the same name already exists for this book
|
|
*/
|
|
public static addNewWorld(userId: string, bookId: string, worldName: string, lang: 'fr' | 'en' = 'fr', existingWorldId?: string, seriesWorldId: string | null = null): string {
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
const hashedWorldName: string = System.hashElement(worldName);
|
|
if (!existingWorldId && WorldRepository.checkWorldExist(userId, bookId, hashedWorldName, lang)) {
|
|
throw new Error(lang === "fr" ? `Tu as déjà un monde ${worldName}.` : `You already have a world named ${worldName}.`);
|
|
}
|
|
const encryptedWorldName: string = System.encryptDataWithUserKey(worldName, userEncryptionKey);
|
|
const worldId: string = existingWorldId || System.createUniqueId();
|
|
return WorldRepository.insertNewWorld(worldId, userId, bookId, encryptedWorldName, hashedWorldName, lang, seriesWorldId);
|
|
}
|
|
|
|
/**
|
|
* Retrieves all worlds and their elements for a specific book.
|
|
* @param userId - The unique identifier of the user
|
|
* @param bookId - The unique identifier of the book
|
|
* @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
|
|
* @returns WorldListResponse containing an array of WorldProps and enabled flag
|
|
*/
|
|
public static getWorlds(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): WorldListResponse {
|
|
const bookTools: BookToolsTable | null = BookRepo.fetchBookTools(userId, bookId, lang);
|
|
const enabled: boolean = bookTools ? bookTools.worlds_enabled === 1 : false;
|
|
|
|
const worldQueryResults: WorldQuery[] = WorldRepository.fetchWorlds(userId, bookId, lang);
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
const worlds: WorldProps[] = [];
|
|
|
|
for (const queryRow of worldQueryResults) {
|
|
const existingWorld: WorldProps | undefined = worlds.find((world: WorldProps) => world.id === queryRow.world_id);
|
|
|
|
if (!existingWorld) {
|
|
const newWorld: WorldProps = {
|
|
id: queryRow.world_id,
|
|
name: System.decryptDataWithUserKey(queryRow.world_name, userEncryptionKey),
|
|
history: queryRow.history ? System.decryptDataWithUserKey(queryRow.history, userEncryptionKey) : '',
|
|
politics: queryRow.politics ? System.decryptDataWithUserKey(queryRow.politics, userEncryptionKey) : '',
|
|
economy: queryRow.economy ? System.decryptDataWithUserKey(queryRow.economy, userEncryptionKey) : '',
|
|
religion: queryRow.religion ? System.decryptDataWithUserKey(queryRow.religion, userEncryptionKey) : '',
|
|
languages: queryRow.languages ? System.decryptDataWithUserKey(queryRow.languages, userEncryptionKey) : '',
|
|
laws: [],
|
|
biomes: [],
|
|
issues: [],
|
|
customs: [],
|
|
kingdoms: [],
|
|
climate: [],
|
|
resources: [],
|
|
wildlife: [],
|
|
arts: [],
|
|
ethnicGroups: [],
|
|
socialClasses: [],
|
|
importantCharacters: [],
|
|
seriesWorldId: queryRow.series_world_id || null,
|
|
};
|
|
|
|
worlds.push(newWorld);
|
|
|
|
if (queryRow.element_type) {
|
|
const worldElement: WorldElement = {
|
|
id: queryRow.element_id as string,
|
|
name: queryRow.element_name ? System.decryptDataWithUserKey(queryRow.element_name, userEncryptionKey) : '',
|
|
description: queryRow.element_description ? System.decryptDataWithUserKey(queryRow.element_description, userEncryptionKey) : ''
|
|
};
|
|
|
|
const elementKey: keyof WorldProps | undefined = ELEMENT_TYPE_KEYS[queryRow.element_type];
|
|
if (elementKey) {
|
|
(worlds[worlds.length - 1][elementKey] as WorldElement[]).push(worldElement);
|
|
}
|
|
}
|
|
} else {
|
|
const worldElement: WorldElement = {
|
|
id: queryRow.element_id as string,
|
|
name: queryRow.element_name ? System.decryptDataWithUserKey(queryRow.element_name, userEncryptionKey) : '',
|
|
description: queryRow.element_description ? System.decryptDataWithUserKey(queryRow.element_description, userEncryptionKey) : ''
|
|
};
|
|
|
|
const elementKey: keyof WorldProps | undefined = ELEMENT_TYPE_KEYS[queryRow.element_type as number];
|
|
if (elementKey) {
|
|
(existingWorld[elementKey] as WorldElement[]).push(worldElement);
|
|
}
|
|
}
|
|
}
|
|
return { worlds, enabled };
|
|
}
|
|
|
|
/**
|
|
* Updates a world's properties and all its elements.
|
|
* @param userId - The unique identifier of the user
|
|
* @param world - The WorldProps object containing updated world data
|
|
* @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
|
|
* @returns True if the update was successful, false otherwise
|
|
*/
|
|
public static updateWorld(userId: string, world: WorldProps, lang: 'fr' | 'en' = 'fr'): boolean {
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
const encryptedName: string = world.name ? System.encryptDataWithUserKey(world.name, userEncryptionKey) : '';
|
|
const encryptedHistory: string = world.history ? System.encryptDataWithUserKey(world.history, userEncryptionKey) : '';
|
|
const encryptedPolitics: string = world.politics ? System.encryptDataWithUserKey(world.politics, userEncryptionKey) : '';
|
|
const encryptedEconomy: string = world.economy ? System.encryptDataWithUserKey(world.economy, userEncryptionKey) : '';
|
|
const encryptedReligion: string = world.religion ? System.encryptDataWithUserKey(world.religion, userEncryptionKey) : '';
|
|
const encryptedLanguages: string = world.languages ? System.encryptDataWithUserKey(world.languages, userEncryptionKey) : '';
|
|
|
|
let elementsToUpdate: WorldElementValue[] = [];
|
|
const elementCategories: { key: keyof WorldProps; elements: WorldElement[] }[] = [
|
|
{ key: 'laws', elements: world.laws },
|
|
{ key: 'biomes', elements: world.biomes },
|
|
{ key: 'issues', elements: world.issues },
|
|
{ key: 'customs', elements: world.customs },
|
|
{ key: 'kingdoms', elements: world.kingdoms },
|
|
{ key: 'climate', elements: world.climate },
|
|
{ key: 'resources', elements: world.resources },
|
|
{ key: 'wildlife', elements: world.wildlife },
|
|
{ key: 'arts', elements: world.arts },
|
|
{ key: 'ethnicGroups', elements: world.ethnicGroups },
|
|
{ key: 'socialClasses', elements: world.socialClasses },
|
|
{ key: 'importantCharacters', elements: world.importantCharacters }
|
|
];
|
|
|
|
elementCategories.forEach(({ key, elements: categoryElements }) => {
|
|
elementsToUpdate = elementsToUpdate.concat(categoryElements.map((worldElement: WorldElement) => {
|
|
const encryptedElementName: string = System.encryptDataWithUserKey(worldElement.name, userEncryptionKey);
|
|
const hashedElementName: string = System.hashElement(worldElement.name);
|
|
const encryptedDescription: string = worldElement.description ? System.encryptDataWithUserKey(worldElement.description, userEncryptionKey) : '';
|
|
const elementTypeId: number = World.getElementTypes(key);
|
|
|
|
return {
|
|
id: worldElement.id,
|
|
name: encryptedElementName,
|
|
hashedName: hashedElementName,
|
|
description: encryptedDescription,
|
|
type: elementTypeId
|
|
};
|
|
}));
|
|
});
|
|
|
|
WorldRepository.updateWorld(userId, world.id, encryptedName, System.hashElement(world.name), encryptedHistory, encryptedPolitics, encryptedEconomy, encryptedReligion, encryptedLanguages, System.timeStampInSeconds(), lang, world.seriesWorldId || null);
|
|
return WorldRepository.updateWorldElements(userId, elementsToUpdate, lang);
|
|
}
|
|
|
|
/**
|
|
* Adds a new element to an existing world.
|
|
* @param userId - The unique identifier of the user
|
|
* @param worldId - The unique identifier of the world to add the element to
|
|
* @param elementName - The name of the new element
|
|
* @param elementType - The type of element (e.g., 'laws', 'biomes', 'customs')
|
|
* @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
|
|
* @param existingElementId - Optional existing element ID for syncing purposes
|
|
* @returns The unique identifier of the newly created element
|
|
* @throws Error if an element with the same name already exists in this world
|
|
*/
|
|
public static addNewElementToWorld(userId: string, worldId: string, elementName: string, elementType: string, lang: 'fr' | 'en' = 'fr', existingElementId?: string): string {
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
const hashedElementName: string = System.hashElement(elementName);
|
|
if (!existingElementId && WorldRepository.checkElementExist(worldId, hashedElementName, lang)) {
|
|
throw new Error(lang === "fr" ? `Vous avez déjà un élément avec ce nom ${elementName}.` : `You already have an element named ${elementName}.`);
|
|
}
|
|
const elementTypeId: number = World.getElementTypes(elementType);
|
|
const encryptedElementName: string = System.encryptDataWithUserKey(elementName, userEncryptionKey);
|
|
const elementId: string = existingElementId || System.createUniqueId();
|
|
return WorldRepository.insertNewElement(userId, elementId, elementTypeId, worldId, encryptedElementName, hashedElementName, lang);
|
|
}
|
|
|
|
/**
|
|
* Converts an element type string key to its corresponding numeric identifier.
|
|
* @param elementType - The element type key (e.g., 'laws', 'biomes', 'customs')
|
|
* @returns The numeric identifier for the element type, or 0 if not found
|
|
*/
|
|
public static getElementTypes(elementType: string): number {
|
|
return ELEMENT_TYPE_MAP[elementType] ?? 0;
|
|
}
|
|
|
|
/**
|
|
* Removes an element from a world.
|
|
* @param userId - The unique identifier of the user
|
|
* @param elementId - The unique identifier of the element to remove
|
|
* @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
|
|
* @returns True if the deletion was successful, false otherwise
|
|
*/
|
|
public static removeElementFromWorld(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
|
return WorldRepository.deleteElement(userId, elementId, lang);
|
|
}
|
|
|
|
}
|