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:
natreex
2025-11-17 09:34:54 -05:00
parent 09768aafcf
commit d5eb1691d9
12 changed files with 2763 additions and 197 deletions

View 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;
}