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.
This commit is contained in:
268
electron/database/models/World.ts
Normal file
268
electron/database/models/World.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
import { getUserEncryptionKey } from "../keyManager.js";
|
||||
import System from "../System.js";
|
||||
import WorldRepository, { WorldElementValue, WorldQuery } from "../repositories/world.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[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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): 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 An array of WorldProps objects containing all world data and their elements
|
||||
*/
|
||||
public static getWorlds(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): WorldProps[] {
|
||||
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: [],
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user