Expand character model with additional attributes and advanced customization options

- Added fields such as `nickname`, `age`, `gender`, `species`, `nationality`, `status`, and others to enhance character customization.
- Modified localization files to include new field labels and placeholders.
- Updated `CharacterComponent` and `CharacterDetail` components with UI elements for the newly added attributes.
- Introduced "Advanced Mode" toggle to manage visibility of extended customization options.
- Refactored database models and repository methods (`addNewCharacter`, `updateCharacter`, and `fetchCharacters`) to handle the extended schema.
- Improved data encryption and decryption workflows for secure storage of added attributes.
- Enhanced user experience by reorganizing character customization layouts.
This commit is contained in:
natreex
2026-01-23 20:49:57 -05:00
parent 57bf0c6ec3
commit 0fbd3743e7
11 changed files with 806 additions and 211 deletions

View File

@@ -7,12 +7,23 @@ export interface BookCharactersTable extends Record<string, SQLiteValue> {
user_id: string;
first_name: string;
last_name: string | null;
nickname: string | null;
age: string | null;
gender: string | null;
species: string | null;
nationality: string | null;
status: string | null;
category: string;
title: string | null;
image: string | null;
role: string | null;
biography: string | null;
history: string | null;
speech_pattern: string | null;
catchphrase: string | null;
residence: string | null;
notes: string | null;
color: string | null;
last_update: number;
}
@@ -43,12 +54,23 @@ export interface CharacterResult extends Record<string, SQLiteValue> {
character_id: string;
first_name: string;
last_name: string;
nickname: string;
age: string;
gender: string;
species: string;
nationality: string;
status: string;
title: string;
category: string;
image: string;
role: string;
biography: string;
history: string;
speech_pattern: string;
catchphrase: string;
residence: string;
notes: string;
color: string;
}
export interface AttributeResult extends Record<string, SQLiteValue> {
@@ -81,7 +103,7 @@ export default class CharacterRepo {
public static fetchCharacters(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): CharacterResult[] {
try {
const db: Database = System.getDb();
const query: string = 'SELECT character_id, first_name, last_name, title, category, image, role, biography, history FROM book_characters WHERE book_id=? AND user_id=?';
const query: string = 'SELECT character_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color FROM book_characters WHERE book_id=? AND user_id=?';
const params: SQLiteValue[] = [bookId, userId];
const characters: CharacterResult[] = db.all(query, params) as CharacterResult[];
return characters;
@@ -100,23 +122,48 @@ export default class CharacterRepo {
* Adds a new character to the database.
* @param userId - The unique identifier of the user
* @param characterId - The unique identifier for the new character
* @param encryptedName - The encrypted first name of the character
* @param encryptedLastName - The encrypted last name of the character
* @param encryptedTitle - The encrypted title of the character
* @param encryptedCategory - The encrypted category of the character
* @param encryptedImage - The encrypted image path of the character
* @param encryptedRole - The encrypted role of the character
* @param encryptedBiography - The encrypted biography of the character
* @param encryptedHistory - The encrypted history of the character
* @param characterData - Object containing all encrypted character fields
* @param bookId - The unique identifier of the book
* @param lang - The language for error messages ('fr' or 'en')
* @returns The character ID if successful
*/
public static addNewCharacter(userId: string, characterId: string, encryptedName: string, encryptedLastName: string, encryptedTitle: string, encryptedCategory: string, encryptedImage: string, encryptedRole: string, encryptedBiography: string, encryptedHistory: string, bookId: string, lang: 'fr' | 'en' = 'fr'): string {
public static addNewCharacter(userId: string, characterId: string, characterData: {
firstName: string;
lastName: string;
nickname: string;
age: string;
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;
}, bookId: string, lang: 'fr' | 'en' = 'fr'): string {
try {
const db: Database = System.getDb();
const query: string = 'INSERT INTO `book_characters` (character_id, book_id, user_id, first_name, last_name, category, title, image, role, biography, history, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)';
const params: SQLiteValue[] = [characterId, bookId, userId, encryptedName, encryptedLastName, encryptedCategory, encryptedTitle, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, System.timeStampInSeconds()];
const query: string = `INSERT INTO book_characters (
character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status,
category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`;
const params: SQLiteValue[] = [
characterId, bookId, userId,
characterData.firstName, characterData.lastName, characterData.nickname,
characterData.age, characterData.gender, characterData.species,
characterData.nationality, characterData.status, characterData.category,
characterData.title, characterData.image, characterData.role,
characterData.biography, characterData.history, characterData.speechPattern,
characterData.catchphrase, characterData.residence, characterData.notes,
characterData.color, System.timeStampInSeconds()
];
const insertResult: RunResult = db.run(query, params);
if (!insertResult || insertResult.changes === 0) {
throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du personnage.` : `Error adding character.`);
@@ -168,23 +215,48 @@ export default class CharacterRepo {
* Updates an existing character's information.
* @param userId - The unique identifier of the user
* @param id - The unique identifier of the character to update
* @param encryptedName - The encrypted first name of the character
* @param encryptedLastName - The encrypted last name of the character
* @param encryptedTitle - The encrypted title of the character
* @param encryptedCategory - The encrypted category of the character
* @param encryptedImage - The encrypted image path of the character
* @param encryptedRole - The encrypted role of the character
* @param encryptedBiography - The encrypted biography of the character
* @param encryptedHistory - The encrypted history of the character
* @param characterData - Object containing all encrypted character fields
* @param lastUpdate - The timestamp of the last update
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if the update was successful, false otherwise
*/
static updateCharacter(userId: string, id: string, encryptedName: string, encryptedLastName: string, encryptedTitle: string, encryptedCategory: string, encryptedImage: string, encryptedRole: string, encryptedBiography: string, encryptedHistory: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
static updateCharacter(userId: string, id: string, characterData: {
firstName: string;
lastName: string;
nickname: string;
age: string;
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;
}, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
try {
const db: Database = System.getDb();
const query: string = 'UPDATE `book_characters` SET `first_name`=?,`last_name`=?,`title`=?,`category`=?,`image`=?,`role`=?,`biography`=?,`history`=?,`last_update`=? WHERE `character_id`=? AND `user_id`=?';
const params: SQLiteValue[] = [encryptedName, encryptedLastName, encryptedTitle, encryptedCategory, encryptedImage, encryptedRole, encryptedBiography, encryptedHistory, lastUpdate, id, userId];
const query: string = `UPDATE book_characters SET
first_name=?, last_name=?, nickname=?, age=?, gender=?, species=?, nationality=?, status=?,
title=?, category=?, image=?, role=?, biography=?, history=?,
speech_pattern=?, catchphrase=?, residence=?, notes=?, color=?, last_update=?
WHERE character_id=? AND user_id=?`;
const params: SQLiteValue[] = [
characterData.firstName, characterData.lastName, characterData.nickname,
characterData.age, characterData.gender, characterData.species,
characterData.nationality, characterData.status, characterData.title,
characterData.category, characterData.image, characterData.role,
characterData.biography, characterData.history, characterData.speechPattern,
characterData.catchphrase, characterData.residence, characterData.notes,
characterData.color, lastUpdate, id, userId
];
const updateResult: RunResult = db.run(query, params);
return updateResult.changes > 0;
} catch (error: unknown) {
@@ -396,7 +468,9 @@ export default class CharacterRepo {
static async fetchBookCharacters(userId: string, bookId: string, lang: 'fr' | 'en'): Promise<BookCharactersTable[]> {
try {
const db: Database = System.getDb();
const query: string = 'SELECT character_id, book_id, user_id, first_name, last_name, category, title, image, role, biography, history, last_update FROM book_characters WHERE user_id=? AND book_id=?';
const query: string = `SELECT character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status,
category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update
FROM book_characters WHERE user_id=? AND book_id=?`;
const params: SQLiteValue[] = [userId, bookId];
const characters: BookCharactersTable[] = db.all(query, params) as BookCharactersTable[];
return characters;
@@ -487,24 +561,48 @@ export default class CharacterRepo {
* @param characterId - The unique identifier of the character
* @param bookId - The unique identifier of the book
* @param userId - The unique identifier of the user
* @param firstName - The first name of the character
* @param lastName - The last name of the character (nullable)
* @param category - The category of the character
* @param title - The title of the character (nullable)
* @param image - The image path of the character (nullable)
* @param role - The role of the character (nullable)
* @param biography - The biography of the character (nullable)
* @param history - The history of the character (nullable)
* @param characterData - Object containing all character fields
* @param lastUpdate - The timestamp of the last update
* @param lang - The language for error messages ('fr' or 'en')
* @returns True if the insertion was successful, false otherwise
*/
static insertSyncCharacter(characterId: string, bookId: string, userId: string, firstName: string, lastName: string | null, category: string, title: string | null, image: string | null, role: string | null, biography: string | null, history: string | null, lastUpdate: number, lang: 'fr' | 'en'): boolean {
static insertSyncCharacter(characterId: string, bookId: string, userId: string, characterData: {
firstName: string;
lastName: string | null;
nickname: string | null;
age: string | null;
gender: string | null;
species: string | null;
nationality: string | null;
status: string | null;
category: string;
title: string | null;
image: string | null;
role: string | null;
biography: string | null;
history: string | null;
speechPattern: string | null;
catchphrase: string | null;
residence: string | null;
notes: string | null;
color: string | null;
}, lastUpdate: number, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const query: string = `INSERT INTO book_characters (character_id, book_id, user_id, first_name, last_name, category, title, image, role, biography, history, last_update)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const params: SQLiteValue[] = [characterId, bookId, userId, firstName, lastName, category, title, image, role, biography, history, lastUpdate];
const query: string = `INSERT INTO book_characters (
character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status,
category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const params: SQLiteValue[] = [
characterId, bookId, userId,
characterData.firstName, characterData.lastName, characterData.nickname,
characterData.age, characterData.gender, characterData.species,
characterData.nationality, characterData.status, characterData.category,
characterData.title, characterData.image, characterData.role,
characterData.biography, characterData.history, characterData.speechPattern,
characterData.catchphrase, characterData.residence, characterData.notes,
characterData.color, lastUpdate
];
const insertResult: RunResult = db.run(query, params);
return insertResult.changes > 0;
} catch (error: unknown) {
@@ -555,7 +653,8 @@ export default class CharacterRepo {
static async fetchCompleteCharacterById(id: string, lang: "fr" | "en"): Promise<BookCharactersTable[]> {
try {
const db: Database = System.getDb();
const query: string = `SELECT character_id, book_id, user_id, first_name, last_name, category, title, image, role, biography, history, last_update
const query: string = `SELECT character_id, book_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status,
category, title, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update
FROM book_characters
WHERE character_id = ?`;
const params: SQLiteValue[] = [id];