- Deleted `CharacterComponent` and `CharacterDetail` files from the project. - Refactored related logic to improve code maintainability and reduce redundancy.
516 lines
28 KiB
TypeScript
516 lines
28 KiB
TypeScript
import CharacterRepo, {
|
|
AttributeResult,
|
|
CharacterResult,
|
|
CompleteCharacterResult
|
|
} from "../repositories/character.repository.js";
|
|
import BookRepo, {BookToolsTable} from "../repositories/book.repository.js";
|
|
import System from "../System.js";
|
|
import {getUserEncryptionKey} from "../keyManager.js";
|
|
import RemovedItem from "./RemovedItem.js";
|
|
|
|
export type CharacterCategory = 'Main' | 'Secondary' | 'Recurring';
|
|
|
|
export interface CharacterPropsPost {
|
|
id: string | null;
|
|
name: string;
|
|
lastName: string;
|
|
nickname: string;
|
|
age: number | null;
|
|
gender: string;
|
|
species: string;
|
|
nationality: string;
|
|
status: string;
|
|
category: CharacterCategory;
|
|
title: string;
|
|
image: string;
|
|
physical: { name: string }[];
|
|
psychological: { name: string }[];
|
|
relations: { name: string }[];
|
|
skills: { name: string }[];
|
|
weaknesses: { name: string }[];
|
|
strengths: { name: string }[];
|
|
goals: { name: string }[];
|
|
motivations: { name: string }[];
|
|
arc: { name: string }[];
|
|
secrets: { name: string }[];
|
|
fears: { name: string }[];
|
|
flaws: { name: string }[];
|
|
beliefs: { name: string }[];
|
|
conflicts: { name: string }[];
|
|
quotes: { name: string }[];
|
|
distinguishingMarks: { name: string }[];
|
|
items: { name: string }[];
|
|
affiliations: { name: string }[];
|
|
role: string;
|
|
biography?: string;
|
|
history?: string;
|
|
speechPattern?: string;
|
|
catchphrase?: string;
|
|
residence?: string;
|
|
notes?: string;
|
|
color?: string;
|
|
seriesCharacterId?: string | null;
|
|
}
|
|
|
|
|
|
export interface CharacterProps {
|
|
id: string;
|
|
name: string;
|
|
lastName: string;
|
|
nickname: string;
|
|
age: number | null;
|
|
gender: string;
|
|
species: string;
|
|
nationality: string;
|
|
status: string;
|
|
title: string;
|
|
category: string;
|
|
image: string;
|
|
role: string;
|
|
biography: string;
|
|
history: string;
|
|
speechPattern: string;
|
|
catchphrase: string;
|
|
residence: string;
|
|
notes: string;
|
|
color: string;
|
|
seriesCharacterId: string | null;
|
|
}
|
|
|
|
export interface CharacterListResponse {
|
|
characters: CharacterProps[];
|
|
enabled: boolean;
|
|
}
|
|
|
|
export interface CompleteCharacterProps {
|
|
id?: string;
|
|
name: string;
|
|
lastName: string;
|
|
nickname: string;
|
|
age: number | null;
|
|
gender: string;
|
|
species: string;
|
|
nationality: string;
|
|
status: string;
|
|
title: string;
|
|
category: string;
|
|
image?: string;
|
|
role: string;
|
|
biography: string;
|
|
history: string;
|
|
speechPattern: string;
|
|
catchphrase: string;
|
|
residence: string;
|
|
notes: string;
|
|
color: string;
|
|
[key: string]: Attribute[] | string | number | null | undefined;
|
|
}
|
|
|
|
export interface Attribute {
|
|
id: string;
|
|
name: string;
|
|
}
|
|
|
|
|
|
export interface CharacterAttribute {
|
|
type: string;
|
|
values: Attribute[];
|
|
}
|
|
|
|
export interface SyncedCharacter {
|
|
id: string;
|
|
name: string;
|
|
lastUpdate: number;
|
|
attributes: SyncedCharacterAttribute[];
|
|
}
|
|
|
|
export interface SyncedCharacterAttribute {
|
|
id: string;
|
|
name: string;
|
|
lastUpdate: number;
|
|
}
|
|
|
|
export default class Character {
|
|
/**
|
|
* Retrieves a list of all characters for a specific book.
|
|
* Decrypts character data using the user's encryption key.
|
|
* @param userId - The unique identifier of the user
|
|
* @param bookId - The unique identifier of the book
|
|
* @param lang - The language code for localization (defaults to 'fr')
|
|
* @returns An array of decrypted character properties
|
|
*/
|
|
public static getCharacterList(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): CharacterListResponse {
|
|
const bookTools: BookToolsTable | null = BookRepo.fetchBookTools(userId, bookId, lang);
|
|
const enabled: boolean = bookTools ? bookTools.characters_enabled === 1 : false;
|
|
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
const encryptedCharacters: CharacterResult[] = CharacterRepo.fetchCharacters(userId, bookId, lang);
|
|
if (!encryptedCharacters || encryptedCharacters.length === 0) {
|
|
return { characters: [], enabled };
|
|
}
|
|
const decryptedCharacterList: CharacterProps[] = [];
|
|
for (const encryptedCharacter of encryptedCharacters) {
|
|
decryptedCharacterList.push({
|
|
id: encryptedCharacter.character_id,
|
|
name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '',
|
|
lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_name, userEncryptionKey) : '',
|
|
nickname: encryptedCharacter.nickname ? System.decryptDataWithUserKey(encryptedCharacter.nickname, userEncryptionKey) : '',
|
|
age: encryptedCharacter.age ? parseInt(System.decryptDataWithUserKey(encryptedCharacter.age, userEncryptionKey), 10) : null,
|
|
gender: encryptedCharacter.gender ? System.decryptDataWithUserKey(encryptedCharacter.gender, userEncryptionKey) : '',
|
|
species: encryptedCharacter.species ? System.decryptDataWithUserKey(encryptedCharacter.species, userEncryptionKey) : '',
|
|
nationality: encryptedCharacter.nationality ? System.decryptDataWithUserKey(encryptedCharacter.nationality, userEncryptionKey) : '',
|
|
status: encryptedCharacter.status ? System.decryptDataWithUserKey(encryptedCharacter.status, userEncryptionKey) : 'alive',
|
|
title: encryptedCharacter.title ? System.decryptDataWithUserKey(encryptedCharacter.title, userEncryptionKey) : '',
|
|
category: encryptedCharacter.category ? System.decryptDataWithUserKey(encryptedCharacter.category, userEncryptionKey) : '',
|
|
image: encryptedCharacter.image ? System.decryptDataWithUserKey(encryptedCharacter.image, userEncryptionKey) : '',
|
|
role: encryptedCharacter.role ? System.decryptDataWithUserKey(encryptedCharacter.role, userEncryptionKey) : '',
|
|
biography: encryptedCharacter.biography ? System.decryptDataWithUserKey(encryptedCharacter.biography, userEncryptionKey) : '',
|
|
history: encryptedCharacter.history ? System.decryptDataWithUserKey(encryptedCharacter.history, userEncryptionKey) : '',
|
|
speechPattern: encryptedCharacter.speech_pattern ? System.decryptDataWithUserKey(encryptedCharacter.speech_pattern, userEncryptionKey) : '',
|
|
catchphrase: encryptedCharacter.catchphrase ? System.decryptDataWithUserKey(encryptedCharacter.catchphrase, userEncryptionKey) : '',
|
|
residence: encryptedCharacter.residence ? System.decryptDataWithUserKey(encryptedCharacter.residence, userEncryptionKey) : '',
|
|
notes: encryptedCharacter.notes ? System.decryptDataWithUserKey(encryptedCharacter.notes, userEncryptionKey) : '',
|
|
color: encryptedCharacter.color ? System.decryptDataWithUserKey(encryptedCharacter.color, userEncryptionKey) : '',
|
|
seriesCharacterId: encryptedCharacter.series_character_id || null,
|
|
})
|
|
}
|
|
return { characters: decryptedCharacterList, enabled };
|
|
}
|
|
|
|
/**
|
|
* Creates a new character with all its attributes for a specific book.
|
|
* Encrypts all character data before storing in the database.
|
|
* @param userId - The unique identifier of the user
|
|
* @param character - The character data to be created
|
|
* @param bookId - The unique identifier of the book
|
|
* @param lang - The language code for localization (defaults to 'fr')
|
|
* @param existingCharacterId - Optional existing character ID for updates or imports
|
|
* @returns The unique identifier of the newly created character
|
|
*/
|
|
public static addNewCharacter(userId: string, character: CharacterPropsPost, bookId: string, lang: 'fr' | 'en' = 'fr', existingCharacterId?: string): string {
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
const characterId: string = existingCharacterId || System.createUniqueId();
|
|
|
|
const characterData = {
|
|
firstName: System.encryptDataWithUserKey(character.name, userEncryptionKey),
|
|
lastName: System.encryptDataWithUserKey(character.lastName, userEncryptionKey),
|
|
nickname: System.encryptDataWithUserKey(character.nickname || '', userEncryptionKey),
|
|
age: character.age !== null ? System.encryptDataWithUserKey(String(character.age), userEncryptionKey) : '',
|
|
gender: System.encryptDataWithUserKey(character.gender || '', userEncryptionKey),
|
|
species: System.encryptDataWithUserKey(character.species || '', userEncryptionKey),
|
|
nationality: System.encryptDataWithUserKey(character.nationality || '', userEncryptionKey),
|
|
status: System.encryptDataWithUserKey(character.status || 'alive', userEncryptionKey),
|
|
title: System.encryptDataWithUserKey(character.title, userEncryptionKey),
|
|
category: System.encryptDataWithUserKey(character.category, userEncryptionKey),
|
|
image: System.encryptDataWithUserKey(character.image, userEncryptionKey),
|
|
role: System.encryptDataWithUserKey(character.role, userEncryptionKey),
|
|
biography: System.encryptDataWithUserKey(character.biography || '', userEncryptionKey),
|
|
history: System.encryptDataWithUserKey(character.history || '', userEncryptionKey),
|
|
speechPattern: System.encryptDataWithUserKey(character.speechPattern || '', userEncryptionKey),
|
|
catchphrase: System.encryptDataWithUserKey(character.catchphrase || '', userEncryptionKey),
|
|
residence: System.encryptDataWithUserKey(character.residence || '', userEncryptionKey),
|
|
notes: System.encryptDataWithUserKey(character.notes || '', userEncryptionKey),
|
|
color: System.encryptDataWithUserKey(character.color || '', userEncryptionKey),
|
|
};
|
|
|
|
CharacterRepo.addNewCharacter(userId, characterId, characterData, bookId, lang, character.seriesCharacterId || null);
|
|
const characterPropertyKeys: string[] = Object.keys(character);
|
|
for (const propertyKey of characterPropertyKeys) {
|
|
if (Array.isArray(character[propertyKey as keyof CharacterPropsPost])) {
|
|
const attributeArray = character[propertyKey as keyof CharacterPropsPost] as { name: string }[];
|
|
if (attributeArray.length > 0) {
|
|
for (const attributeItem of attributeArray) {
|
|
const attributeType: string = propertyKey;
|
|
const attributeName: string = attributeItem.name;
|
|
this.addNewAttribute(characterId, userId, attributeType, attributeName, lang);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return characterId;
|
|
}
|
|
|
|
/**
|
|
* Updates an existing character's core properties.
|
|
* Encrypts all updated data before storing in the database.
|
|
* @param userId - The unique identifier of the user
|
|
* @param character - The character data with updated values
|
|
* @param lang - The language code for localization (defaults to 'fr')
|
|
* @returns True if the update was successful, false otherwise
|
|
*/
|
|
static updateCharacter(userId: string, character: CharacterPropsPost, lang: 'fr' | 'en' = 'fr'): boolean {
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
if (!character.id) {
|
|
return false;
|
|
}
|
|
|
|
const characterData = {
|
|
firstName: System.encryptDataWithUserKey(character.name, userEncryptionKey),
|
|
lastName: System.encryptDataWithUserKey(character.lastName, userEncryptionKey),
|
|
nickname: System.encryptDataWithUserKey(character.nickname || '', userEncryptionKey),
|
|
age: character.age !== null ? System.encryptDataWithUserKey(String(character.age), userEncryptionKey) : '',
|
|
gender: System.encryptDataWithUserKey(character.gender || '', userEncryptionKey),
|
|
species: System.encryptDataWithUserKey(character.species || '', userEncryptionKey),
|
|
nationality: System.encryptDataWithUserKey(character.nationality || '', userEncryptionKey),
|
|
status: System.encryptDataWithUserKey(character.status || 'alive', userEncryptionKey),
|
|
title: System.encryptDataWithUserKey(character.title, userEncryptionKey),
|
|
category: System.encryptDataWithUserKey(character.category, userEncryptionKey),
|
|
image: System.encryptDataWithUserKey(character.image, userEncryptionKey),
|
|
role: System.encryptDataWithUserKey(character.role, userEncryptionKey),
|
|
biography: System.encryptDataWithUserKey(character.biography || '', userEncryptionKey),
|
|
history: System.encryptDataWithUserKey(character.history || '', userEncryptionKey),
|
|
speechPattern: System.encryptDataWithUserKey(character.speechPattern || '', userEncryptionKey),
|
|
catchphrase: System.encryptDataWithUserKey(character.catchphrase || '', userEncryptionKey),
|
|
residence: System.encryptDataWithUserKey(character.residence || '', userEncryptionKey),
|
|
notes: System.encryptDataWithUserKey(character.notes || '', userEncryptionKey),
|
|
color: System.encryptDataWithUserKey(character.color || '', userEncryptionKey),
|
|
};
|
|
|
|
return CharacterRepo.updateCharacter(userId, character.id, characterData, System.timeStampInSeconds(), lang, character.seriesCharacterId || null);
|
|
}
|
|
|
|
/**
|
|
* Adds a new attribute to a character.
|
|
* Attributes are categorized properties like physical traits, skills, or goals.
|
|
* @param characterId - The unique identifier of the character
|
|
* @param userId - The unique identifier of the user
|
|
* @param type - The type/category of the attribute (e.g., 'physical', 'skills')
|
|
* @param name - The value/name of the attribute
|
|
* @param lang - The language code for localization (defaults to 'fr')
|
|
* @param existingAttributeId - Optional existing attribute ID for updates or imports
|
|
* @returns The unique identifier of the newly created attribute
|
|
*/
|
|
static addNewAttribute(characterId: string, userId: string, type: string, name: string, lang: 'fr' | 'en' = 'fr', existingAttributeId?: string): string {
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
const attributeId: string = existingAttributeId || System.createUniqueId();
|
|
const encryptedType: string = System.encryptDataWithUserKey(type, userEncryptionKey);
|
|
const encryptedName: string = System.encryptDataWithUserKey(name, userEncryptionKey);
|
|
return CharacterRepo.insertAttribute(attributeId, characterId, userId, encryptedType, encryptedName, lang);
|
|
}
|
|
|
|
/**
|
|
* Deletes an attribute from a character.
|
|
* @param userId - The unique identifier of the user
|
|
* @param bookId - The unique identifier of the book
|
|
* @param attributeId - The unique identifier of the attribute to delete
|
|
* @param deletedAt - The timestamp of deletion
|
|
* @param lang - The language code for localization (defaults to 'fr')
|
|
* @returns True if the deletion was successful, false otherwise
|
|
*/
|
|
static deleteAttribute(userId: string, bookId: string, attributeId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
|
const deleted: boolean = CharacterRepo.deleteAttribute(userId, attributeId, lang);
|
|
if (deleted) {
|
|
RemovedItem.deleteTracker(userId, bookId, 'book_characters_attributes', attributeId, deletedAt, lang);
|
|
}
|
|
return deleted;
|
|
}
|
|
|
|
/**
|
|
* Deletes a character and all its related data.
|
|
* @param userId - The unique identifier of the user
|
|
* @param bookId - The unique identifier of the book
|
|
* @param characterId - The unique identifier of the character to delete
|
|
* @param deletedAt - The timestamp of deletion
|
|
* @param lang - The language code for localization (defaults to 'fr')
|
|
* @returns True if the deletion was successful
|
|
*/
|
|
static deleteCharacter(userId: string, bookId: string, characterId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
|
const deleted: boolean = CharacterRepo.deleteCharacter(userId, characterId, lang);
|
|
if (deleted) {
|
|
RemovedItem.deleteTracker(userId, bookId, 'book_characters', characterId, deletedAt, lang);
|
|
}
|
|
return deleted;
|
|
}
|
|
|
|
/**
|
|
* Retrieves all attributes for a specific character, grouped by type.
|
|
* Decrypts attribute data using the user's encryption key.
|
|
* @param characterId - The unique identifier of the character
|
|
* @param userId - The unique identifier of the user
|
|
* @param lang - The language code for localization (defaults to 'fr')
|
|
* @returns An array of character attributes grouped by type
|
|
*/
|
|
static getAttributes(characterId: string, userId: string, lang: 'fr' | 'en' = 'fr'): CharacterAttribute[] {
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
const encryptedAttributes: AttributeResult[] = CharacterRepo.fetchAttributes(characterId, userId, lang);
|
|
if (!encryptedAttributes?.length) return [];
|
|
|
|
const attributesByType: Map<string, Attribute[]> = new Map<string, Attribute[]>();
|
|
|
|
for (const encryptedAttribute of encryptedAttributes) {
|
|
const decryptedType: string = System.decryptDataWithUserKey(encryptedAttribute.attribute_name, userEncryptionKey);
|
|
const decryptedValue: string = encryptedAttribute.attribute_value ? System.decryptDataWithUserKey(encryptedAttribute.attribute_value, userEncryptionKey) : '';
|
|
|
|
if (!attributesByType.has(decryptedType)) {
|
|
attributesByType.set(decryptedType, []);
|
|
}
|
|
|
|
attributesByType.get(decryptedType)!.push({
|
|
id: encryptedAttribute.attr_id,
|
|
name: decryptedValue
|
|
});
|
|
}
|
|
|
|
return Array.from<[string, Attribute[]], CharacterAttribute>(
|
|
attributesByType,
|
|
([type, values]: [string, Attribute[]]): CharacterAttribute => ({type, values})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Retrieves complete character data including all attributes for multiple characters.
|
|
* Used for exporting or displaying full character profiles.
|
|
* @param userId - The unique identifier of the user
|
|
* @param bookId - The unique identifier of the book
|
|
* @param characters - An array of character IDs to retrieve
|
|
* @param lang - The language code for localization (defaults to 'fr')
|
|
* @returns An array of complete character objects with all their attributes
|
|
*/
|
|
static getCompleteCharacterList(userId: string, bookId: string, characters: string[], lang: 'fr' | 'en' = 'fr'): CompleteCharacterProps[] {
|
|
const encryptedCharacterList: CompleteCharacterResult[] = CharacterRepo.fetchCompleteCharacters(userId, bookId, characters, lang);
|
|
|
|
if (!encryptedCharacterList || encryptedCharacterList.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
|
const completeCharactersMap = new Map<string, CompleteCharacterProps>();
|
|
for (const encryptedCharacter of encryptedCharacterList) {
|
|
if (!encryptedCharacter.character_id) {
|
|
continue;
|
|
}
|
|
|
|
if (!completeCharactersMap.has(encryptedCharacter.character_id)) {
|
|
const decryptedCharacter: CompleteCharacterProps = {
|
|
id: '',
|
|
name: encryptedCharacter.first_name ? System.decryptDataWithUserKey(encryptedCharacter.first_name, userEncryptionKey) : '',
|
|
lastName: encryptedCharacter.last_name ? System.decryptDataWithUserKey(encryptedCharacter.last_name, userEncryptionKey) : '',
|
|
nickname: encryptedCharacter.nickname ? System.decryptDataWithUserKey(encryptedCharacter.nickname as string, userEncryptionKey) : '',
|
|
age: encryptedCharacter.age ? parseInt(System.decryptDataWithUserKey(encryptedCharacter.age as string, userEncryptionKey), 10) : null,
|
|
gender: encryptedCharacter.gender ? System.decryptDataWithUserKey(encryptedCharacter.gender as string, userEncryptionKey) : '',
|
|
species: encryptedCharacter.species ? System.decryptDataWithUserKey(encryptedCharacter.species as string, userEncryptionKey) : '',
|
|
nationality: encryptedCharacter.nationality ? System.decryptDataWithUserKey(encryptedCharacter.nationality as string, userEncryptionKey) : '',
|
|
status: encryptedCharacter.status ? System.decryptDataWithUserKey(encryptedCharacter.status as string, userEncryptionKey) : 'alive',
|
|
title: encryptedCharacter.title ? System.decryptDataWithUserKey(encryptedCharacter.title as string, userEncryptionKey) : '',
|
|
category: encryptedCharacter.category ? System.decryptDataWithUserKey(encryptedCharacter.category as string, userEncryptionKey) : '',
|
|
role: encryptedCharacter.role ? System.decryptDataWithUserKey(encryptedCharacter.role as string, userEncryptionKey) : '',
|
|
biography: encryptedCharacter.biography ? System.decryptDataWithUserKey(encryptedCharacter.biography as string, userEncryptionKey) : '',
|
|
history: encryptedCharacter.history ? System.decryptDataWithUserKey(encryptedCharacter.history as string, userEncryptionKey) : '',
|
|
speechPattern: encryptedCharacter.speech_pattern ? System.decryptDataWithUserKey(encryptedCharacter.speech_pattern as string, userEncryptionKey) : '',
|
|
catchphrase: encryptedCharacter.catchphrase ? System.decryptDataWithUserKey(encryptedCharacter.catchphrase as string, userEncryptionKey) : '',
|
|
residence: encryptedCharacter.residence ? System.decryptDataWithUserKey(encryptedCharacter.residence as string, userEncryptionKey) : '',
|
|
notes: encryptedCharacter.notes ? System.decryptDataWithUserKey(encryptedCharacter.notes as string, userEncryptionKey) : '',
|
|
color: encryptedCharacter.color ? System.decryptDataWithUserKey(encryptedCharacter.color as string, userEncryptionKey) : '',
|
|
physical: [],
|
|
psychological: [],
|
|
relations: [],
|
|
skills: [],
|
|
weaknesses: [],
|
|
strengths: [],
|
|
goals: [],
|
|
motivations: [],
|
|
arc: [],
|
|
secrets: [],
|
|
fears: [],
|
|
flaws: [],
|
|
beliefs: [],
|
|
conflicts: [],
|
|
quotes: [],
|
|
distinguishingMarks: [],
|
|
items: [],
|
|
affiliations: []
|
|
};
|
|
completeCharactersMap.set(encryptedCharacter.character_id, decryptedCharacter);
|
|
}
|
|
|
|
const characterEntry: CompleteCharacterProps | undefined = completeCharactersMap.get(encryptedCharacter.character_id);
|
|
|
|
if (!encryptedCharacter.attribute_name || !characterEntry) {
|
|
continue;
|
|
}
|
|
const decryptedAttributeName: string = System.decryptDataWithUserKey(encryptedCharacter.attribute_name, userEncryptionKey);
|
|
const decryptedAttributeValue: string = encryptedCharacter.attribute_value ? System.decryptDataWithUserKey(encryptedCharacter.attribute_value, userEncryptionKey) : '';
|
|
|
|
if (Array.isArray(characterEntry[decryptedAttributeName])) {
|
|
characterEntry[decryptedAttributeName].push({
|
|
id: '',
|
|
name: decryptedAttributeValue
|
|
});
|
|
}
|
|
}
|
|
return Array.from(completeCharactersMap.values());
|
|
}
|
|
|
|
/**
|
|
* Generates a formatted vCard-style string representation of characters.
|
|
* Useful for AI context or text-based exports.
|
|
* @param characters - An array of complete character objects to format
|
|
* @returns A formatted string containing all character information
|
|
*/
|
|
static characterVCard(characters: CompleteCharacterProps[]): string {
|
|
const uniqueCharactersMap = new Map<string, CompleteCharacterProps>();
|
|
let formattedCharactersDescription: string = '';
|
|
|
|
characters.forEach((character: CompleteCharacterProps): void => {
|
|
const characterIdentifier: string = character.name || character.id || 'unknown';
|
|
|
|
if (!uniqueCharactersMap.has(characterIdentifier)) {
|
|
uniqueCharactersMap.set(characterIdentifier, {
|
|
name: character.name,
|
|
lastName: character.lastName,
|
|
nickname: character.nickname,
|
|
age: character.age,
|
|
gender: character.gender,
|
|
species: character.species,
|
|
nationality: character.nationality,
|
|
status: character.status,
|
|
title: character.title,
|
|
category: character.category,
|
|
role: character.role,
|
|
biography: character.biography,
|
|
history: character.history,
|
|
speechPattern: character.speechPattern,
|
|
catchphrase: character.catchphrase,
|
|
residence: character.residence,
|
|
notes: character.notes,
|
|
color: character.color
|
|
});
|
|
}
|
|
|
|
const aggregatedCharacterData: CompleteCharacterProps = uniqueCharactersMap.get(characterIdentifier)!;
|
|
|
|
Object.keys(character).forEach((propertyName: string): void => {
|
|
if (Array.isArray(character[propertyName])) {
|
|
if (!aggregatedCharacterData[propertyName]) aggregatedCharacterData[propertyName] = [];
|
|
(aggregatedCharacterData[propertyName] as Attribute[]).push(...(character[propertyName] as Attribute[]));
|
|
}
|
|
});
|
|
});
|
|
|
|
formattedCharactersDescription = Array.from(uniqueCharactersMap.values()).map((character: CompleteCharacterProps): string => {
|
|
const characterDescriptionLines: string[] = [];
|
|
const fullName: string = [character.name, character.lastName].filter(Boolean).join(' ');
|
|
if (fullName) characterDescriptionLines.push(`Nom : ${fullName}`);
|
|
|
|
(['category', 'title', 'role', 'biography', 'history'] as const).forEach((propertyKey) => {
|
|
if (character[propertyKey]) {
|
|
characterDescriptionLines.push(`${propertyKey.charAt(0).toUpperCase() + propertyKey.slice(1)} : ${character[propertyKey]}`);
|
|
}
|
|
});
|
|
|
|
Object.keys(character).forEach((propertyKey: string): void => {
|
|
const propertyValue = character[propertyKey];
|
|
if (Array.isArray(propertyValue) && propertyValue.length > 0) {
|
|
const capitalizedPropertyKey: string = propertyKey.charAt(0).toUpperCase() + propertyKey.slice(1);
|
|
const formattedAttributeValues: string = propertyValue.map((attributeItem: Attribute) => attributeItem.name).join(', ');
|
|
characterDescriptionLines.push(`${capitalizedPropertyKey} : ${formattedAttributeValues}`);
|
|
}
|
|
});
|
|
|
|
return characterDescriptionLines.join('\n');
|
|
}).join('\n\n');
|
|
return formattedCharactersDescription;
|
|
}
|
|
|
|
}
|