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,120 @@
/**
* TypeScript interfaces (copied from lib/models for type safety)
*/
export interface Message {
id: number;
type: string;
message: string;
date: string;
}
export interface Conversation {
id: string;
title?: string;
date?: string;
type?: string;
status: number;
totalPrice: number;
messages: Message[];
}
export interface ConversationProps {
id: string;
mode: string;
title: string;
startDate: string;
status: number;
}
/**
* Database row types (snake_case from SQLite)
*/
export interface DBConversation {
conversation_id: string;
book_id: string;
mode: string;
title: string;
start_date: number; // Unix timestamp
status: number;
user_id: string;
summary?: string;
convo_meta: string;
synced?: number;
}
export interface DBMessage {
message_id: string;
conversation_id: string;
role: string; // 'user' or 'model'
message: string;
message_date: number; // Unix timestamp
meta_message: string;
synced?: number;
}
/**
* MAPPERS: DB → TypeScript Interfaces
*/
export function dbToConversation(dbConvo: DBConversation, messages: DBMessage[] = []): Conversation {
return {
id: dbConvo.conversation_id,
title: dbConvo.title,
date: new Date(dbConvo.start_date).toISOString(),
type: dbConvo.mode as any,
status: dbConvo.status,
totalPrice: 0, // Computed from messages if needed
messages: messages.map(dbToMessage)
};
}
export function dbToConversationProps(dbConvo: DBConversation): ConversationProps {
return {
id: dbConvo.conversation_id,
mode: dbConvo.mode,
title: dbConvo.title,
startDate: new Date(dbConvo.start_date).toISOString(),
status: dbConvo.status
};
}
export function dbToMessage(dbMessage: DBMessage): Message {
return {
id: parseInt(dbMessage.message_id, 10) || 0,
type: dbMessage.role as any,
message: dbMessage.message,
date: new Date(dbMessage.message_date).toISOString()
};
}
/**
* MAPPERS: TypeScript Interfaces → DB
*/
export function conversationToDb(conversation: Conversation, bookId: string, userId: string, synced: number = 0): DBConversation {
return {
conversation_id: conversation.id,
book_id: bookId,
mode: conversation.type || 'chatbot',
title: conversation.title || 'Untitled Conversation',
start_date: conversation.date ? new Date(conversation.date).getTime() : Date.now(),
status: conversation.status,
user_id: userId,
summary: '',
convo_meta: '',
synced
};
}
export function messageToDb(message: Message, conversationId: string, synced: number = 0): DBMessage {
return {
message_id: message.id.toString(),
conversation_id: conversationId,
role: message.type,
message: message.message,
message_date: message.date ? new Date(message.date).getTime() : Date.now(),
meta_message: '',
synced
};
}

View File

@@ -0,0 +1,406 @@
/**
* TypeScript interfaces (copied from lib/models for type safety)
*/
export interface Author {
id: string;
name: string;
lastName: string;
authorName?: string;
}
export interface ActChapter {
chapterInfoId: string;
chapterId: string;
title: string;
chapterOrder: number;
actId: number;
incidentId?: string;
plotPointId?: string;
summary: string;
goal: string;
}
export interface ChapterProps {
chapterId: string;
chapterOrder: number;
title: string;
chapterContent: ChapterContent;
}
export interface ChapterContent {
version: number;
content: string;
wordsCount: number;
}
export interface BookProps {
bookId: string;
type: string;
title: string;
author?: Author;
serie?: number;
subTitle?: string;
summary?: string;
publicationDate?: string;
desiredWordCount?: number;
totalWordCount?: number;
coverImage?: string;
chapters?: ChapterProps[];
}
export interface BookListProps {
id: string;
type: string;
authorId: string;
title: string;
subTitle?: string;
summary?: string;
serieId?: number;
desiredReleaseDate?: string;
desiredWordCount?: number;
wordCount?: number;
coverImage?: string;
bookMeta?: string;
}
export interface GuideLine {
tone: string;
atmosphere: string;
writingStyle: string;
themes: string;
symbolism: string;
motifs: string;
narrativeVoice: string;
pacing: string;
intendedAudience: string;
keyMessages: string;
}
export interface GuideLineAI {
narrativeType: number;
dialogueType: number;
globalResume: string;
atmosphere: string;
verbeTense: number;
langue: number;
themes: string;
}
export interface PlotPoint {
plotPointId: string;
title: string;
summary: string;
linkedIncidentId: string;
chapters?: ActChapter[];
}
export interface Incident {
incidentId: string;
title: string;
summary: string;
chapters?: ActChapter[];
}
export interface Issue {
id: string;
name: string;
}
/**
* Database row types (snake_case from SQLite)
*/
export interface DBBook {
book_id: string;
type: string;
author_id: string;
title: string;
hashed_title: string;
sub_title?: string;
hashed_sub_title: string;
summary: string;
serie_id?: number;
desired_release_date?: string;
desired_word_count?: number;
words_count?: number;
cover_image?: string;
book_meta?: string;
synced?: number;
}
export interface DBGuideLine {
user_id: string;
book_id: string;
tone: string;
atmosphere: string;
writing_style: string;
themes: string;
symbolism: string;
motifs: string;
narrative_voice: string;
pacing: string;
intended_audience: string;
key_messages: string;
meta_guide_line: string;
synced?: number;
}
export interface DBGuideLineAI {
user_id: string;
book_id: string;
global_resume: string;
themes: string;
verbe_tense: number;
narrative_type: number;
langue: number;
dialogue_type: number;
tone: string;
atmosphere: string;
current_resume: string;
meta: string;
synced?: number;
}
export interface DBPlotPoint {
plot_point_id: string;
title: string;
hashed_title: string;
summary?: string;
linked_incident_id?: string;
author_id: string;
book_id: string;
meta_plot: string;
synced?: number;
}
export interface DBIncident {
incident_id: string;
author_id: string;
book_id: string;
title: string;
hashed_title: string;
summary?: string;
meta_incident: string;
synced?: number;
}
export interface DBIssue {
issue_id: string;
author_id: string;
book_id: string;
name: string;
hashed_issue_name: string;
meta_issue: string;
synced?: number;
}
/**
* MAPPERS: DB → TypeScript Interfaces
*/
export function dbToBookList(dbBook: DBBook): BookListProps {
return {
id: dbBook.book_id,
type: dbBook.type,
authorId: dbBook.author_id,
title: dbBook.title,
subTitle: dbBook.sub_title,
summary: dbBook.summary,
serieId: dbBook.serie_id,
desiredReleaseDate: dbBook.desired_release_date,
desiredWordCount: dbBook.desired_word_count,
wordCount: dbBook.words_count,
coverImage: dbBook.cover_image,
bookMeta: dbBook.book_meta
};
}
export function dbToBook(dbBook: DBBook, author?: Author): BookProps {
return {
bookId: dbBook.book_id,
type: dbBook.type,
title: dbBook.title,
author,
serie: dbBook.serie_id,
subTitle: dbBook.sub_title,
summary: dbBook.summary,
publicationDate: dbBook.desired_release_date,
desiredWordCount: dbBook.desired_word_count,
totalWordCount: dbBook.words_count,
coverImage: dbBook.cover_image,
chapters: [] // Populated separately
};
}
export function dbToGuideLine(dbGuideLine: DBGuideLine): GuideLine {
return {
tone: dbGuideLine.tone,
atmosphere: dbGuideLine.atmosphere,
writingStyle: dbGuideLine.writing_style,
themes: dbGuideLine.themes,
symbolism: dbGuideLine.symbolism,
motifs: dbGuideLine.motifs,
narrativeVoice: dbGuideLine.narrative_voice,
pacing: dbGuideLine.pacing,
intendedAudience: dbGuideLine.intended_audience,
keyMessages: dbGuideLine.key_messages
};
}
export function dbToGuideLineAI(dbGuideLineAI: DBGuideLineAI): GuideLineAI {
return {
narrativeType: dbGuideLineAI.narrative_type,
dialogueType: dbGuideLineAI.dialogue_type,
globalResume: dbGuideLineAI.global_resume,
atmosphere: dbGuideLineAI.atmosphere,
verbeTense: dbGuideLineAI.verbe_tense,
langue: dbGuideLineAI.langue,
themes: dbGuideLineAI.themes
};
}
export function dbToPlotPoint(dbPlotPoint: DBPlotPoint): PlotPoint {
return {
plotPointId: dbPlotPoint.plot_point_id,
title: dbPlotPoint.title,
summary: dbPlotPoint.summary || '',
linkedIncidentId: dbPlotPoint.linked_incident_id || '',
chapters: [] // Populated separately
};
}
export function dbToIncident(dbIncident: DBIncident): Incident {
return {
incidentId: dbIncident.incident_id,
title: dbIncident.title,
summary: dbIncident.summary || '',
chapters: [] // Populated separately
};
}
export function dbToIssue(dbIssue: DBIssue): Issue {
return {
id: dbIssue.issue_id,
name: dbIssue.name
};
}
/**
* MAPPERS: TypeScript Interfaces → DB
*/
export function bookListToDb(book: BookListProps, synced: number = 0): DBBook {
return {
book_id: book.id,
type: book.type,
author_id: book.authorId,
title: book.title,
hashed_title: '', // Will be computed with hash function
sub_title: book.subTitle,
hashed_sub_title: '',
summary: book.summary || '',
serie_id: book.serieId,
desired_release_date: book.desiredReleaseDate,
desired_word_count: book.desiredWordCount,
words_count: book.wordCount,
cover_image: book.coverImage,
book_meta: book.bookMeta || '',
synced
};
}
export function bookToDb(book: BookProps, authorId: string, synced: number = 0): DBBook {
return {
book_id: book.bookId,
type: book.type,
author_id: authorId,
title: book.title,
hashed_title: '',
sub_title: book.subTitle,
hashed_sub_title: '',
summary: book.summary || '',
serie_id: book.serie,
desired_release_date: book.publicationDate,
desired_word_count: book.desiredWordCount,
words_count: book.totalWordCount,
cover_image: book.coverImage,
book_meta: '',
synced
};
}
export function guideLineToDb(guideLine: GuideLine, userId: string, bookId: string, synced: number = 0): DBGuideLine {
return {
user_id: userId,
book_id: bookId,
tone: guideLine.tone,
atmosphere: guideLine.atmosphere,
writing_style: guideLine.writingStyle,
themes: guideLine.themes,
symbolism: guideLine.symbolism,
motifs: guideLine.motifs,
narrative_voice: guideLine.narrativeVoice,
pacing: guideLine.pacing,
intended_audience: guideLine.intendedAudience,
key_messages: guideLine.keyMessages,
meta_guide_line: '',
synced
};
}
export function guideLineAIToDb(guideLineAI: GuideLineAI, userId: string, bookId: string, synced: number = 0): DBGuideLineAI {
return {
user_id: userId,
book_id: bookId,
global_resume: guideLineAI.globalResume,
themes: guideLineAI.themes,
verbe_tense: guideLineAI.verbeTense,
narrative_type: guideLineAI.narrativeType,
langue: guideLineAI.langue,
dialogue_type: guideLineAI.dialogueType,
tone: '',
atmosphere: guideLineAI.atmosphere,
current_resume: '',
meta: '',
synced
};
}
export function plotPointToDb(plotPoint: PlotPoint, authorId: string, bookId: string, synced: number = 0): DBPlotPoint {
return {
plot_point_id: plotPoint.plotPointId,
title: plotPoint.title,
hashed_title: '',
summary: plotPoint.summary,
linked_incident_id: plotPoint.linkedIncidentId,
author_id: authorId,
book_id: bookId,
meta_plot: '',
synced
};
}
export function incidentToDb(incident: Incident, authorId: string, bookId: string, synced: number = 0): DBIncident {
return {
incident_id: incident.incidentId,
author_id: authorId,
book_id: bookId,
title: incident.title,
hashed_title: '',
summary: incident.summary,
meta_incident: '',
synced
};
}
export function issueToDb(issue: Issue, authorId: string, bookId: string, synced: number = 0): DBIssue {
return {
issue_id: issue.id,
author_id: authorId,
book_id: bookId,
name: issue.name,
hashed_issue_name: '',
meta_issue: '',
synced
};
}

View File

@@ -0,0 +1,174 @@
/**
* TypeScript interfaces (copied from lib/models for type safety)
*/
export interface ChapterContent {
version: number;
content: string;
wordsCount: number;
}
export interface ChapterProps {
chapterId: string;
chapterOrder: number;
title: string;
chapterContent: ChapterContent;
}
export interface ActChapter {
chapterInfoId: string;
chapterId: string;
title: string;
chapterOrder: number;
actId: number;
incidentId?: string;
plotPointId?: string;
summary: string;
goal: string;
}
/**
* Database row types (snake_case from SQLite)
*/
export interface DBChapter {
chapter_id: string;
book_id: string;
author_id: string;
title: string;
hashed_title?: string;
words_count?: number;
chapter_order?: number;
meta_chapter: string;
synced?: number;
}
export interface DBChapterContent {
content_id: string;
chapter_id: string;
author_id: string;
version: number;
content: string;
words_count: number;
meta_chapter_content: string;
time_on_it: number;
synced?: number;
}
export interface DBChapterInfo {
chapter_info_id: string;
chapter_id?: string;
act_id?: number;
incident_id?: string;
plot_point_id?: string;
book_id?: string;
author_id?: string;
summary: string;
goal: string;
meta_chapter_info: string;
synced?: number;
}
/**
* MAPPERS: DB → TypeScript Interfaces
*/
export function dbToChapter(dbChapter: DBChapter, dbContent?: DBChapterContent): ChapterProps {
const chapterContent: ChapterContent = dbContent ? {
version: dbContent.version,
content: dbContent.content,
wordsCount: dbContent.words_count
} : {
version: 2,
content: '',
wordsCount: 0
};
return {
chapterId: dbChapter.chapter_id,
chapterOrder: dbChapter.chapter_order || 0,
title: dbChapter.title,
chapterContent
};
}
export function dbToChapterContent(dbContent: DBChapterContent): ChapterContent {
return {
version: dbContent.version,
content: dbContent.content,
wordsCount: dbContent.words_count
};
}
export function dbToActChapter(dbChapter: DBChapter, dbInfo: DBChapterInfo): ActChapter {
return {
chapterInfoId: dbInfo.chapter_info_id,
chapterId: dbChapter.chapter_id,
title: dbChapter.title,
chapterOrder: dbChapter.chapter_order || 0,
actId: dbInfo.act_id || 0,
incidentId: dbInfo.incident_id,
plotPointId: dbInfo.plot_point_id,
summary: dbInfo.summary,
goal: dbInfo.goal
};
}
/**
* MAPPERS: TypeScript Interfaces → DB
*/
export function chapterToDb(chapter: ChapterProps, bookId: string, authorId: string, synced: number = 0): DBChapter {
return {
chapter_id: chapter.chapterId,
book_id: bookId,
author_id: authorId,
title: chapter.title,
hashed_title: '',
words_count: chapter.chapterContent.wordsCount,
chapter_order: chapter.chapterOrder,
meta_chapter: '',
synced
};
}
export function chapterContentToDb(
content: ChapterContent,
contentId: string,
chapterId: string,
authorId: string,
timeOnIt: number = 0,
synced: number = 0
): DBChapterContent {
return {
content_id: contentId,
chapter_id: chapterId,
author_id: authorId,
version: content.version,
content: content.content,
words_count: content.wordsCount,
meta_chapter_content: '',
time_on_it: timeOnIt,
synced
};
}
export function actChapterToDbInfo(
actChapter: ActChapter,
bookId: string,
authorId: string,
synced: number = 0
): DBChapterInfo {
return {
chapter_info_id: actChapter.chapterInfoId,
chapter_id: actChapter.chapterId,
act_id: actChapter.actId,
incident_id: actChapter.incidentId,
plot_point_id: actChapter.plotPointId,
book_id: bookId,
author_id: authorId,
summary: actChapter.summary,
goal: actChapter.goal,
meta_chapter_info: '',
synced
};
}

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

View File

@@ -0,0 +1,153 @@
/**
* TypeScript interfaces (copied from lib/models for type safety)
*/
export interface Subscription {
subType: string;
subTier: number;
status: boolean;
}
export interface UserProps {
id: string;
name: string;
lastName: string;
username: string;
authorName?: string;
email?: string;
accountVerified: boolean;
termsAccepted: boolean;
aiUsage: number;
apiKeys: {
gemini: boolean;
openai: boolean;
anthropic: boolean;
};
books?: any[];
guideTour?: { [key: string]: boolean }[];
subscription?: Subscription[];
writingLang: number;
writingLevel: number;
ritePoints: number;
creditsBalance: number;
groupId: number;
}
/**
* Database row types (snake_case from SQLite)
*/
export interface DBUser {
user_id: string;
first_name: string;
last_name: string;
username: string;
email: string;
origin_email: string;
origin_username: string;
author_name?: string;
origin_author_name?: string;
plateform: string;
social_id?: string;
user_group: number;
password?: string;
term_accepted: number;
verify_code?: string;
reg_date: number;
account_verified: number;
user_meta: string; // JSON containing apiKeys, guideTour, writingLang, writingLevel, aiUsage
erite_points: number;
stripe_customer_id?: string;
credits_balance: number;
synced?: number;
}
interface UserMeta {
apiKeys?: {
gemini: boolean;
openai: boolean;
anthropic: boolean;
};
guideTour?: { [key: string]: boolean }[];
subscription?: Subscription[];
writingLang?: number;
writingLevel?: number;
aiUsage?: number;
}
/**
* MAPPERS: DB → TypeScript Interfaces
*/
export function dbToUser(dbUser: DBUser): UserProps {
let meta: UserMeta = {};
try {
meta = JSON.parse(dbUser.user_meta || '{}');
} catch (error) {
console.error('Failed to parse user_meta:', error);
}
return {
id: dbUser.user_id,
name: dbUser.first_name,
lastName: dbUser.last_name,
username: dbUser.username,
authorName: dbUser.author_name,
email: dbUser.email,
accountVerified: dbUser.account_verified === 1,
termsAccepted: dbUser.term_accepted === 1,
aiUsage: meta.aiUsage || 0,
apiKeys: meta.apiKeys || {
gemini: false,
openai: false,
anthropic: false
},
books: [], // Populated separately
guideTour: meta.guideTour || [],
subscription: meta.subscription || [],
writingLang: meta.writingLang || 1,
writingLevel: meta.writingLevel || 1,
ritePoints: dbUser.erite_points,
creditsBalance: dbUser.credits_balance,
groupId: dbUser.user_group
};
}
/**
* MAPPERS: TypeScript Interfaces → DB
*/
export function userToDb(user: UserProps, synced: number = 0): DBUser {
const meta: UserMeta = {
apiKeys: user.apiKeys,
guideTour: user.guideTour,
subscription: user.subscription,
writingLang: user.writingLang,
writingLevel: user.writingLevel,
aiUsage: user.aiUsage
};
return {
user_id: user.id,
first_name: user.name,
last_name: user.lastName,
username: user.username,
email: user.email || '',
origin_email: user.email || '',
origin_username: user.username,
author_name: user.authorName,
origin_author_name: user.authorName,
plateform: 'electron',
social_id: undefined,
user_group: user.groupId,
password: undefined,
term_accepted: user.termsAccepted ? 1 : 0,
verify_code: undefined,
reg_date: Date.now(),
account_verified: user.accountVerified ? 1 : 0,
user_meta: JSON.stringify(meta),
erite_points: user.ritePoints,
stripe_customer_id: undefined,
credits_balance: user.creditsBalance,
synced
};
}

View File

@@ -0,0 +1,222 @@
/**
* TypeScript interfaces (copied from lib/models for type safety)
*/
export interface WorldElement {
id: string;
name: string;
description: string;
}
export interface WorldProps {
id: string;
name: string;
history: string;
politics: string;
economy: string;
religion: string;
languages: string;
laws?: WorldElement[];
biomes?: WorldElement[];
issues?: WorldElement[];
customs?: WorldElement[];
kingdoms?: WorldElement[];
climate?: WorldElement[];
resources?: WorldElement[];
wildlife?: WorldElement[];
arts?: WorldElement[];
ethnicGroups?: WorldElement[];
socialClasses?: WorldElement[];
importantCharacters?: WorldElement[];
}
/**
* Database row types (snake_case from SQLite)
*/
export interface DBWorld {
world_id: string;
name: string;
hashed_name: string;
author_id: string;
book_id: string;
history?: string;
politics?: string;
economy?: string;
religion?: string;
languages?: string;
meta_world: string;
synced?: number;
}
export interface DBWorldElement {
element_id: string;
world_id: string;
user_id: string;
element_type: number; // Type identifier for different element categories
name: string;
original_name: string;
description?: string;
meta_element: string;
synced?: number;
}
// Element type constants
export enum WorldElementType {
LAW = 1,
BIOME = 2,
ISSUE = 3,
CUSTOM = 4,
KINGDOM = 5,
CLIMATE = 6,
RESOURCE = 7,
WILDLIFE = 8,
ART = 9,
ETHNIC_GROUP = 10,
SOCIAL_CLASS = 11,
IMPORTANT_CHARACTER = 12
}
/**
* MAPPERS: DB → TypeScript Interfaces
*/
export function dbToWorld(dbWorld: DBWorld, elements: DBWorldElement[] = []): WorldProps {
// Group elements by type
const laws: WorldElement[] = [];
const biomes: WorldElement[] = [];
const issues: WorldElement[] = [];
const customs: WorldElement[] = [];
const kingdoms: WorldElement[] = [];
const climate: WorldElement[] = [];
const resources: WorldElement[] = [];
const wildlife: WorldElement[] = [];
const arts: WorldElement[] = [];
const ethnicGroups: WorldElement[] = [];
const socialClasses: WorldElement[] = [];
const importantCharacters: WorldElement[] = [];
for (const elem of elements) {
const worldElement: WorldElement = {
id: elem.element_id,
name: elem.name,
description: elem.description || ''
};
switch (elem.element_type) {
case WorldElementType.LAW:
laws.push(worldElement);
break;
case WorldElementType.BIOME:
biomes.push(worldElement);
break;
case WorldElementType.ISSUE:
issues.push(worldElement);
break;
case WorldElementType.CUSTOM:
customs.push(worldElement);
break;
case WorldElementType.KINGDOM:
kingdoms.push(worldElement);
break;
case WorldElementType.CLIMATE:
climate.push(worldElement);
break;
case WorldElementType.RESOURCE:
resources.push(worldElement);
break;
case WorldElementType.WILDLIFE:
wildlife.push(worldElement);
break;
case WorldElementType.ART:
arts.push(worldElement);
break;
case WorldElementType.ETHNIC_GROUP:
ethnicGroups.push(worldElement);
break;
case WorldElementType.SOCIAL_CLASS:
socialClasses.push(worldElement);
break;
case WorldElementType.IMPORTANT_CHARACTER:
importantCharacters.push(worldElement);
break;
}
}
return {
id: dbWorld.world_id,
name: dbWorld.name,
history: dbWorld.history || '',
politics: dbWorld.politics || '',
economy: dbWorld.economy || '',
religion: dbWorld.religion || '',
languages: dbWorld.languages || '',
laws,
biomes,
issues,
customs,
kingdoms,
climate,
resources,
wildlife,
arts,
ethnicGroups,
socialClasses,
importantCharacters
};
}
/**
* MAPPERS: TypeScript Interfaces → DB
*/
export function worldToDb(world: WorldProps, authorId: string, bookId: string, synced: number = 0): DBWorld {
return {
world_id: world.id,
name: world.name,
hashed_name: '',
author_id: authorId,
book_id: bookId,
history: world.history,
politics: world.politics,
economy: world.economy,
religion: world.religion,
languages: world.languages,
meta_world: '',
synced
};
}
export function worldElementsToDb(world: WorldProps, userId: string, synced: number = 0): DBWorldElement[] {
const elements: DBWorldElement[] = [];
const addElements = (type: WorldElementType, elems: WorldElement[]) => {
for (const elem of elems) {
elements.push({
element_id: elem.id,
world_id: world.id,
user_id: userId,
element_type: type,
name: elem.name,
original_name: elem.name,
description: elem.description,
meta_element: '',
synced
});
}
};
addElements(WorldElementType.LAW, world.laws || []);
addElements(WorldElementType.BIOME, world.biomes || []);
addElements(WorldElementType.ISSUE, world.issues || []);
addElements(WorldElementType.CUSTOM, world.customs || []);
addElements(WorldElementType.KINGDOM, world.kingdoms || []);
addElements(WorldElementType.CLIMATE, world.climate || []);
addElements(WorldElementType.RESOURCE, world.resources || []);
addElements(WorldElementType.WILDLIFE, world.wildlife || []);
addElements(WorldElementType.ART, world.arts || []);
addElements(WorldElementType.ETHNIC_GROUP, world.ethnicGroups || []);
addElements(WorldElementType.SOCIAL_CLASS, world.socialClasses || []);
addElements(WorldElementType.IMPORTANT_CHARACTER, world.importantCharacters || []);
return elements;
}