- Implement `schema.ts` for SQLite schema creation, indexing, and sync metadata initialization. - Develop `encryption.ts` with AES-256-GCM encryption utilities for securing database data. - Add `database.service.ts` to manage CRUD operations with encryption support, user-specific databases, and schema initialization. - Integrate book, chapter, and character operations with encrypted content handling and sync preparation.
186 lines
5.4 KiB
TypeScript
186 lines
5.4 KiB
TypeScript
/**
|
|
* TypeScript interfaces (copied from lib/models for type safety)
|
|
*/
|
|
|
|
export interface Attribute {
|
|
id?: string;
|
|
name: string;
|
|
description: string;
|
|
}
|
|
|
|
export interface CharacterProps {
|
|
id: string | null;
|
|
name: string;
|
|
lastName: string;
|
|
category: string;
|
|
title: string;
|
|
image: string;
|
|
physical?: Attribute[];
|
|
psychological?: Attribute[];
|
|
relations?: Attribute[];
|
|
skills?: Attribute[];
|
|
weaknesses?: Attribute[];
|
|
strengths?: Attribute[];
|
|
goals?: Attribute[];
|
|
motivations?: Attribute[];
|
|
role: string;
|
|
biography?: string;
|
|
history?: string;
|
|
}
|
|
|
|
/**
|
|
* Database row types (snake_case from SQLite)
|
|
*/
|
|
export interface DBCharacter {
|
|
character_id: string;
|
|
book_id: string;
|
|
user_id: string;
|
|
first_name: string;
|
|
last_name?: string;
|
|
category: string;
|
|
title?: string;
|
|
image?: string;
|
|
role?: string;
|
|
biography?: string;
|
|
history?: string;
|
|
char_meta: string;
|
|
synced?: number;
|
|
}
|
|
|
|
export interface DBCharacterAttribute {
|
|
attr_id: string;
|
|
character_id: string;
|
|
user_id: string;
|
|
attribute_name: string; // Format: "section:attributeName" (e.g., "physical:Height")
|
|
attribute_value: string; // JSON stringified Attribute
|
|
attr_meta: string;
|
|
synced?: number;
|
|
}
|
|
|
|
/**
|
|
* MAPPERS: DB → TypeScript Interfaces
|
|
*/
|
|
|
|
export function dbToCharacter(dbChar: DBCharacter, attributes: DBCharacterAttribute[] = []): CharacterProps {
|
|
// Group attributes by section
|
|
const physical: Attribute[] = [];
|
|
const psychological: Attribute[] = [];
|
|
const relations: Attribute[] = [];
|
|
const skills: Attribute[] = [];
|
|
const weaknesses: Attribute[] = [];
|
|
const strengths: Attribute[] = [];
|
|
const goals: Attribute[] = [];
|
|
const motivations: Attribute[] = [];
|
|
|
|
for (const attr of attributes) {
|
|
try {
|
|
const parsedValue: Attribute = JSON.parse(attr.attribute_value);
|
|
const section = attr.attribute_name.split(':')[0];
|
|
|
|
switch (section) {
|
|
case 'physical':
|
|
physical.push(parsedValue);
|
|
break;
|
|
case 'psychological':
|
|
psychological.push(parsedValue);
|
|
break;
|
|
case 'relations':
|
|
relations.push(parsedValue);
|
|
break;
|
|
case 'skills':
|
|
skills.push(parsedValue);
|
|
break;
|
|
case 'weaknesses':
|
|
weaknesses.push(parsedValue);
|
|
break;
|
|
case 'strengths':
|
|
strengths.push(parsedValue);
|
|
break;
|
|
case 'goals':
|
|
goals.push(parsedValue);
|
|
break;
|
|
case 'motivations':
|
|
motivations.push(parsedValue);
|
|
break;
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to parse character attribute:', error);
|
|
}
|
|
}
|
|
|
|
return {
|
|
id: dbChar.character_id,
|
|
name: dbChar.first_name,
|
|
lastName: dbChar.last_name || '',
|
|
category: dbChar.category as any,
|
|
title: dbChar.title || '',
|
|
image: dbChar.image || '',
|
|
physical,
|
|
psychological,
|
|
relations,
|
|
skills,
|
|
weaknesses,
|
|
strengths,
|
|
goals,
|
|
motivations,
|
|
role: dbChar.role || '',
|
|
biography: dbChar.biography,
|
|
history: dbChar.history
|
|
};
|
|
}
|
|
|
|
/**
|
|
* MAPPERS: TypeScript Interfaces → DB
|
|
*/
|
|
|
|
export function characterToDb(character: CharacterProps, bookId: string, userId: string, synced: number = 0): DBCharacter {
|
|
return {
|
|
character_id: character.id || crypto.randomUUID(),
|
|
book_id: bookId,
|
|
user_id: userId,
|
|
first_name: character.name,
|
|
last_name: character.lastName,
|
|
category: character.category,
|
|
title: character.title,
|
|
image: character.image,
|
|
role: character.role,
|
|
biography: character.biography,
|
|
history: character.history,
|
|
char_meta: '',
|
|
synced
|
|
};
|
|
}
|
|
|
|
export function characterAttributesToDb(
|
|
character: CharacterProps,
|
|
userId: string,
|
|
synced: number = 0
|
|
): DBCharacterAttribute[] {
|
|
const attributes: DBCharacterAttribute[] = [];
|
|
|
|
const addAttributes = (section: string, attrs: Attribute[]) => {
|
|
for (const attr of attrs) {
|
|
attributes.push({
|
|
attr_id: attr.id || crypto.randomUUID(),
|
|
character_id: character.id || '',
|
|
user_id: userId,
|
|
attribute_name: `${section}:${attr.name}`,
|
|
attribute_value: JSON.stringify(attr),
|
|
attr_meta: '',
|
|
synced
|
|
});
|
|
}
|
|
};
|
|
|
|
addAttributes('physical', character.physical || []);
|
|
addAttributes('psychological', character.psychological || []);
|
|
addAttributes('relations', character.relations || []);
|
|
addAttributes('skills', character.skills || []);
|
|
addAttributes('weaknesses', character.weaknesses || []);
|
|
addAttributes('strengths', character.strengths || []);
|
|
addAttributes('goals', character.goals || []);
|
|
addAttributes('motivations', character.motivations || []);
|
|
|
|
return attributes;
|
|
}
|