Add database schema, encryption utilities, and local database service
- 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.
This commit is contained in:
185
electron/database/mappers/character.mapper.ts
Normal file
185
electron/database/mappers/character.mapper.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user