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:
120
electron/database/mappers/ai.mapper.ts
Normal file
120
electron/database/mappers/ai.mapper.ts
Normal 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
|
||||
};
|
||||
}
|
||||
406
electron/database/mappers/book.mapper.ts
Normal file
406
electron/database/mappers/book.mapper.ts
Normal 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
|
||||
};
|
||||
}
|
||||
174
electron/database/mappers/chapter.mapper.ts
Normal file
174
electron/database/mappers/chapter.mapper.ts
Normal 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
|
||||
};
|
||||
}
|
||||
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;
|
||||
}
|
||||
153
electron/database/mappers/user.mapper.ts
Normal file
153
electron/database/mappers/user.mapper.ts
Normal 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
|
||||
};
|
||||
}
|
||||
222
electron/database/mappers/world.mapper.ts
Normal file
222
electron/database/mappers/world.mapper.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user