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:
@@ -25,22 +25,42 @@ export interface LocationProps {
|
||||
elements: Element[];
|
||||
}
|
||||
|
||||
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 default class Location {
|
||||
/**
|
||||
* Récupère toutes les locations pour un utilisateur et un livre donnés.
|
||||
* @param {string} userId - L'ID de l'utilisateur.
|
||||
* @param {string} bookId - L'ID du livre.
|
||||
* @returns {LocationProps[]} - Un tableau de propriétés de location.
|
||||
* @throws {Error} - Lance une erreur si une exception se produit lors de la récupération des locations.
|
||||
* Retrieves all locations for a given user and book.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param bookId - The book's unique identifier.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns An array of location properties with their elements and sub-elements.
|
||||
*/
|
||||
static getAllLocations(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): LocationProps[] {
|
||||
const locations: LocationQueryResult[] = LocationRepo.getLocation(userId, bookId, lang);
|
||||
if (!locations || locations.length === 0) return [];
|
||||
const locationRecords: LocationQueryResult[] = LocationRepo.getLocation(userId, bookId, lang);
|
||||
if (!locationRecords || locationRecords.length === 0) return [];
|
||||
const userKey: string = getUserEncryptionKey(userId);
|
||||
|
||||
const locationArray: LocationProps[] = [];
|
||||
|
||||
for (const record of locations) {
|
||||
for (const record of locationRecords) {
|
||||
let location = locationArray.find(loc => loc.id === record.loc_id);
|
||||
|
||||
if (!location) {
|
||||
@@ -57,12 +77,12 @@ export default class Location {
|
||||
let element = location.elements.find(elem => elem.id === record.element_id);
|
||||
if (!element) {
|
||||
const decryptedName: string = System.decryptDataWithUserKey(record.element_name, userKey);
|
||||
const decryptedDesc: string = record.element_description ? System.decryptDataWithUserKey(record.element_description, userKey) : '';
|
||||
const decryptedDescription: string = record.element_description ? System.decryptDataWithUserKey(record.element_description, userKey) : '';
|
||||
|
||||
element = {
|
||||
id: record.element_id,
|
||||
name: decryptedName,
|
||||
description: decryptedDesc,
|
||||
description: decryptedDescription,
|
||||
subElements: []
|
||||
};
|
||||
location.elements.push(element);
|
||||
@@ -73,13 +93,12 @@ export default class Location {
|
||||
|
||||
if (!subElementExists) {
|
||||
const decryptedName: string = System.decryptDataWithUserKey(record.sub_elem_name, userKey);
|
||||
const decryptedDesc: string = record.sub_elem_description ? System.decryptDataWithUserKey(record.sub_elem_description, userKey) : '';
|
||||
|
||||
const decryptedDescription: string = record.sub_elem_description ? System.decryptDataWithUserKey(record.sub_elem_description, userKey) : '';
|
||||
|
||||
element.subElements.push({
|
||||
id: record.sub_element_id,
|
||||
name: decryptedName,
|
||||
description: decryptedDesc
|
||||
description: decryptedDescription
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -88,47 +107,81 @@ export default class Location {
|
||||
return locationArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new location section for a book.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param locationName - The name of the location to create.
|
||||
* @param bookId - The book's unique identifier.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @param existingLocationId - Optional existing location ID to use instead of generating a new one.
|
||||
* @returns The ID of the created location.
|
||||
*/
|
||||
static addLocationSection(userId: string, locationName: string, bookId: string, lang: 'fr' | 'en' = 'fr', existingLocationId?: string): string {
|
||||
const userKey: string = getUserEncryptionKey(userId);
|
||||
const originalName: string = System.hashElement(locationName);
|
||||
const hashedName: string = System.hashElement(locationName);
|
||||
const encryptedName: string = System.encryptDataWithUserKey(locationName, userKey);
|
||||
const locationId: string = existingLocationId || System.createUniqueId();
|
||||
return LocationRepo.insertLocation(userId, locationId, bookId, encryptedName, originalName, lang);
|
||||
return LocationRepo.insertLocation(userId, locationId, bookId, encryptedName, hashedName, lang);
|
||||
}
|
||||
|
||||
static addLocationElement(userId: string, locationId: string, elementName: string, lang: 'fr' | 'en' = 'fr', existingElementId?: string) {
|
||||
/**
|
||||
* Adds a new element to a location.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param locationId - The parent location's unique identifier.
|
||||
* @param elementName - The name of the element to create.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @param existingElementId - Optional existing element ID to use instead of generating a new one.
|
||||
* @returns The result of the insert operation.
|
||||
*/
|
||||
static addLocationElement(userId: string, locationId: string, elementName: string, lang: 'fr' | 'en' = 'fr', existingElementId?: string): string {
|
||||
const userKey: string = getUserEncryptionKey(userId);
|
||||
const originalName: string = System.hashElement(elementName);
|
||||
const hashedName: string = System.hashElement(elementName);
|
||||
const encryptedName: string = System.encryptDataWithUserKey(elementName, userKey);
|
||||
const elementId: string = existingElementId || System.createUniqueId();
|
||||
return LocationRepo.insertLocationElement(userId, elementId, locationId, encryptedName, originalName, lang)
|
||||
return LocationRepo.insertLocationElement(userId, elementId, locationId, encryptedName, hashedName, lang)
|
||||
}
|
||||
|
||||
static addLocationSubElement(userId: string, elementId: string, subElementName: string, lang: 'fr' | 'en' = 'fr', existingSubElementId?: string) {
|
||||
/**
|
||||
* Adds a new sub-element to a location element.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param elementId - The parent element's unique identifier.
|
||||
* @param subElementName - The name of the sub-element to create.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @param existingSubElementId - Optional existing sub-element ID to use instead of generating a new one.
|
||||
* @returns The result of the insert operation.
|
||||
*/
|
||||
static addLocationSubElement(userId: string, elementId: string, subElementName: string, lang: 'fr' | 'en' = 'fr', existingSubElementId?: string): string {
|
||||
const userKey: string = getUserEncryptionKey(userId);
|
||||
const originalName: string = System.hashElement(subElementName);
|
||||
const hashedName: string = System.hashElement(subElementName);
|
||||
const encryptedName: string = System.encryptDataWithUserKey(subElementName, userKey);
|
||||
const subElementId: string = existingSubElementId || System.createUniqueId();
|
||||
return LocationRepo.insertLocationSubElement(userId, subElementId, elementId, encryptedName, originalName, lang)
|
||||
return LocationRepo.insertLocationSubElement(userId, subElementId, elementId, encryptedName, hashedName, lang)
|
||||
}
|
||||
|
||||
static updateLocationSection(userId: string, locations: LocationProps[], lang: 'fr' | 'en' = 'fr') {
|
||||
/**
|
||||
* Updates multiple location sections along with their elements and sub-elements.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param locations - Array of location properties to update.
|
||||
* @param lang - The language for response messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns An object indicating success and a localized message.
|
||||
*/
|
||||
static updateLocationSection(userId: string, locations: LocationProps[], lang: 'fr' | 'en' = 'fr'): { valid: boolean; message: string } {
|
||||
const userKey: string = getUserEncryptionKey(userId);
|
||||
|
||||
for (const location of locations) {
|
||||
const originalName: string = System.hashElement(location.name);
|
||||
const encryptedName: string = System.encryptDataWithUserKey(location.name, userKey);
|
||||
LocationRepo.updateLocationSection(userId, location.id, encryptedName, originalName, System.timeStampInSeconds(),lang)
|
||||
const hashedLocationName: string = System.hashElement(location.name);
|
||||
const encryptedLocationName: string = System.encryptDataWithUserKey(location.name, userKey);
|
||||
LocationRepo.updateLocationSection(userId, location.id, encryptedLocationName, hashedLocationName, System.timeStampInSeconds(), lang)
|
||||
for (const element of location.elements) {
|
||||
const originalName: string = System.hashElement(element.name);
|
||||
const encryptedName: string = System.encryptDataWithUserKey(element.name, userKey);
|
||||
const encryptDescription: string = element.description ? System.encryptDataWithUserKey(element.description, userKey) : '';
|
||||
LocationRepo.updateLocationElement(userId, element.id, encryptedName, originalName, encryptDescription, System.timeStampInSeconds(), lang)
|
||||
const hashedElementName: string = System.hashElement(element.name);
|
||||
const encryptedElementName: string = System.encryptDataWithUserKey(element.name, userKey);
|
||||
const encryptedElementDescription: string = element.description ? System.encryptDataWithUserKey(element.description, userKey) : '';
|
||||
LocationRepo.updateLocationElement(userId, element.id, encryptedElementName, hashedElementName, encryptedElementDescription, System.timeStampInSeconds(), lang)
|
||||
for (const subElement of element.subElements) {
|
||||
const originalName: string = System.hashElement(subElement.name);
|
||||
const encryptedName: string = System.encryptDataWithUserKey(subElement.name, userKey);
|
||||
const encryptDescription: string = subElement.description ? System.encryptDataWithUserKey(subElement.description, userKey) : '';
|
||||
LocationRepo.updateLocationSubElement(userId, subElement.id, encryptedName, originalName, encryptDescription,System.timeStampInSeconds(),lang)
|
||||
const hashedSubElementName: string = System.hashElement(subElement.name);
|
||||
const encryptedSubElementName: string = System.encryptDataWithUserKey(subElement.name, userKey);
|
||||
const encryptedSubElementDescription: string = subElement.description ? System.encryptDataWithUserKey(subElement.description, userKey) : '';
|
||||
LocationRepo.updateLocationSubElement(userId, subElement.id, encryptedSubElementName, hashedSubElementName, encryptedSubElementDescription, System.timeStampInSeconds(), lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,32 +191,61 @@ export default class Location {
|
||||
}
|
||||
}
|
||||
|
||||
static deleteLocationSection(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr') {
|
||||
/**
|
||||
* Deletes a location section and all its associated elements and sub-elements.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param locationId - The location's unique identifier to delete.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns The result of the delete operation.
|
||||
*/
|
||||
static deleteLocationSection(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): { valid: boolean; message: string } {
|
||||
return LocationRepo.deleteLocationSection(userId, locationId, lang);
|
||||
}
|
||||
|
||||
static deleteLocationElement(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr') {
|
||||
/**
|
||||
* Deletes a location element and all its associated sub-elements.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param elementId - The element's unique identifier to delete.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns The result of the delete operation.
|
||||
*/
|
||||
static deleteLocationElement(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): { valid: boolean; message: string } {
|
||||
return LocationRepo.deleteLocationElement(userId, elementId, lang);
|
||||
}
|
||||
|
||||
static deleteLocationSubElement(userId: string, subElementId: string, lang: 'fr' | 'en' = 'fr') {
|
||||
/**
|
||||
* Deletes a location sub-element.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param subElementId - The sub-element's unique identifier to delete.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns The result of the delete operation.
|
||||
*/
|
||||
static deleteLocationSubElement(userId: string, subElementId: string, lang: 'fr' | 'en' = 'fr'): { valid: boolean; message: string } {
|
||||
return LocationRepo.deleteLocationSubElement(userId, subElementId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves location tags (elements or sub-elements) for tagging purposes.
|
||||
* Returns sub-elements when an element has multiple sub-elements, otherwise returns the element itself.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param bookId - The book's unique identifier.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns An array of sub-elements suitable for tagging.
|
||||
*/
|
||||
static getLocationTags(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): SubElement[] {
|
||||
const data: LocationElementQueryResult[] = LocationRepo.fetchLocationTags(userId, bookId, lang);
|
||||
if (!data || data.length === 0) return [];
|
||||
const tagRecords: LocationElementQueryResult[] = LocationRepo.fetchLocationTags(userId, bookId, lang);
|
||||
if (!tagRecords || tagRecords.length === 0) return [];
|
||||
const userKey: string = getUserEncryptionKey(userId);
|
||||
|
||||
const elementCounts = new Map<string, number>();
|
||||
data.forEach((record: LocationElementQueryResult): void => {
|
||||
tagRecords.forEach((record: LocationElementQueryResult): void => {
|
||||
elementCounts.set(record.element_id, (elementCounts.get(record.element_id) || 0) + 1);
|
||||
});
|
||||
|
||||
const subElements: SubElement[] = [];
|
||||
const processedIds = new Set<string>();
|
||||
|
||||
for (const record of data) {
|
||||
for (const record of tagRecords) {
|
||||
const elementCount: number = elementCounts.get(record.element_id) || 0;
|
||||
|
||||
if (elementCount > 1 && record.sub_element_id) {
|
||||
@@ -189,46 +271,58 @@ export default class Location {
|
||||
return subElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves location elements filtered by specific tag IDs.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param locations - Array of location tag IDs to filter by.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns An array of elements with their associated sub-elements.
|
||||
*/
|
||||
static getLocationsByTags(userId: string, locations: string[], lang: 'fr' | 'en' = 'fr'): Element[] {
|
||||
const locationsTags: LocationByTagResult[] = LocationRepo.fetchLocationsByTags(userId, locations, lang);
|
||||
if (!locationsTags || locationsTags.length === 0) return [];
|
||||
const locationTagRecords: LocationByTagResult[] = LocationRepo.fetchLocationsByTags(userId, locations, lang);
|
||||
if (!locationTagRecords || locationTagRecords.length === 0) return [];
|
||||
const userKey: string = getUserEncryptionKey(userId);
|
||||
const locationTags: Element[] = [];
|
||||
for (const record of locationsTags) {
|
||||
let element: Element | undefined = locationTags.find((elem: Element): boolean => elem.name === record.element_name);
|
||||
const locationElements: Element[] = [];
|
||||
for (const record of locationTagRecords) {
|
||||
let element: Element | undefined = locationElements.find((elem: Element): boolean => elem.name === record.element_name);
|
||||
if (!element) {
|
||||
const decryptedName: string = System.decryptDataWithUserKey(record.element_name, userKey);
|
||||
const decryptedDesc: string = record.element_description ? System.decryptDataWithUserKey(record.element_description, userKey) : '';
|
||||
const decryptedDescription: string = record.element_description ? System.decryptDataWithUserKey(record.element_description, userKey) : '';
|
||||
element = {
|
||||
id: '',
|
||||
name: decryptedName,
|
||||
description: decryptedDesc,
|
||||
description: decryptedDescription,
|
||||
subElements: []
|
||||
};
|
||||
locationTags.push(element);
|
||||
locationElements.push(element);
|
||||
}
|
||||
if (record.sub_elem_name) {
|
||||
const subElementExists: boolean = element.subElements.some(sub => sub.name === record.sub_elem_name);
|
||||
if (!subElementExists) {
|
||||
const decryptedName: string = System.decryptDataWithUserKey(record.sub_elem_name, userKey);
|
||||
const decryptedDesc: string = record.sub_elem_description ? System.decryptDataWithUserKey(record.sub_elem_description, userKey) : '';
|
||||
const decryptedDescription: string = record.sub_elem_description ? System.decryptDataWithUserKey(record.sub_elem_description, userKey) : '';
|
||||
element.subElements.push({
|
||||
id: '',
|
||||
name: decryptedName,
|
||||
description: decryptedDesc
|
||||
description: decryptedDescription
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return locationTags;
|
||||
return locationElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a formatted description string from an array of location elements.
|
||||
* @param locations - Array of location elements to describe.
|
||||
* @returns A formatted string with location names and descriptions.
|
||||
*/
|
||||
static locationsDescription(locations: Element[]): string {
|
||||
return locations.map((location: Element): string => {
|
||||
const fields: string[] = [];
|
||||
if (location.name) fields.push(`Nom : ${location.name}`);
|
||||
if (location.description) fields.push(`Description : ${location.description}`);
|
||||
return fields.join('\n');
|
||||
const descriptionFields: string[] = [];
|
||||
if (location.name) descriptionFields.push(`Nom : ${location.name}`);
|
||||
if (location.description) descriptionFields.push(`Description : ${location.description}`);
|
||||
return descriptionFields.join('\n');
|
||||
}).join('\n\n');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user