Remove all database mappers and README file

This commit is contained in:
natreex
2025-11-17 19:36:38 -05:00
parent d5eb1691d9
commit a8693973e0
11 changed files with 1205 additions and 1828 deletions

View File

@@ -2,31 +2,14 @@ import sqlite3 from 'node-sqlite3-wasm';
import path from 'path';
import { app } from 'electron';
import { initializeSchema } from './schema.js';
import { encrypt, decrypt, encryptObject, decryptObject, hash } from './encryption.js';
// Type alias for compatibility
type Database = sqlite3.Database;
// Mappers
import * as BookMapper from './mappers/book.mapper.js';
import * as ChapterMapper from './mappers/chapter.mapper.js';
import * as CharacterMapper from './mappers/character.mapper.js';
import * as AIMapper from './mappers/ai.mapper.js';
import * as UserMapper from './mappers/user.mapper.js';
import * as WorldMapper from './mappers/world.mapper.js';
// Types from mappers (which contain all necessary interfaces)
import type { BookProps, BookListProps } from './mappers/book.mapper.js';
import type { ChapterProps } from './mappers/chapter.mapper.js';
import type { CharacterProps } from './mappers/character.mapper.js';
import type { Conversation, Message } from './mappers/ai.mapper.js';
import type { UserProps } from './mappers/user.mapper.js';
import type { WorldProps } from './mappers/world.mapper.js';
export type Database = sqlite3.Database;
/**
* DatabaseService - Handles all local database operations
* Provides CRUD operations with automatic encryption/decryption
* Maps between DB snake_case and TypeScript camelCase interfaces
* DatabaseService - Manages SQLite database connection ONLY
* No business logic, no CRUD operations
* Just connection management and encryption key storage
*/
export class DatabaseService {
private db: Database | null = null;
@@ -79,370 +62,25 @@ export class DatabaseService {
}
/**
* Encrypt sensitive field
* Get database connection
* Use this in repositories and model classes
*/
private encryptField(data: string): string {
if (!this.userEncryptionKey) throw new Error('Encryption key not set');
const encrypted = encrypt(data, this.userEncryptionKey);
return JSON.stringify(encrypted);
getDb(): Database | null {
return this.db;
}
/**
* Decrypt sensitive field
* Get user encryption key
*/
private decryptField(encryptedData: string): string {
if (!this.userEncryptionKey) throw new Error('Encryption key not set');
try {
const parsed = JSON.parse(encryptedData);
return decrypt(parsed, this.userEncryptionKey);
} catch {
// If not encrypted (for migration), return as-is
return encryptedData;
}
}
// ========== BOOK OPERATIONS ==========
/**
* Get all books for the current user
*/
getBooks(): BookListProps[] {
if (!this.db || !this.userId) throw new Error('Database not initialized');
const rows = this.db.all(`
SELECT * FROM erit_books
WHERE author_id = ?
ORDER BY book_id DESC
`, [this.userId]) as unknown as BookMapper.DBBook[];
return rows.map(row => BookMapper.dbToBookList(row));
getEncryptionKey(): string | null {
return this.userEncryptionKey;
}
/**
* Get a single book by ID with all related data
* Get current user ID
*/
getBook(bookId: string): BookProps | null {
if (!this.db) throw new Error('Database not initialized');
const rows = this.db.all('SELECT * FROM erit_books WHERE book_id = ?', [bookId]) as unknown as BookMapper.DBBook[];
const row = rows[0];
if (!row) return null;
const book = BookMapper.dbToBook(row);
// Load chapters
const chapterRows = this.db.all(`
SELECT * FROM book_chapters
WHERE book_id = ?
ORDER BY chapter_order ASC
`, [bookId]) as unknown as ChapterMapper.DBChapter[];
book.chapters = chapterRows.map(chapterRow => {
// Load chapter content
const contentRows = this.db!.all(`
SELECT * FROM book_chapter_content
WHERE chapter_id = ?
ORDER BY version DESC
LIMIT 1
`, [chapterRow.chapter_id]) as unknown as ChapterMapper.DBChapterContent[];
const contentRow = contentRows[0];
// Decrypt content if encrypted
if (contentRow && contentRow.content) {
try {
contentRow.content = this.decryptField(contentRow.content);
} catch (error) {
console.warn('Failed to decrypt chapter content:', error);
}
}
return ChapterMapper.dbToChapter(chapterRow, contentRow);
});
return book;
}
/**
* Save or update a book
*/
saveBook(book: BookProps | BookListProps, authorId?: string): void {
if (!this.db || !this.userId) throw new Error('Database not initialized');
const dbBook = 'bookId' in book
? BookMapper.bookToDb(book, authorId || this.userId, 0)
: BookMapper.bookListToDb(book, 0);
// Hash the title
dbBook.hashed_title = hash(dbBook.title);
if (dbBook.sub_title) {
dbBook.hashed_sub_title = hash(dbBook.sub_title);
}
this.db.run(`
INSERT OR REPLACE INTO erit_books (
book_id, type, author_id, title, hashed_title, sub_title, hashed_sub_title,
summary, serie_id, desired_release_date, desired_word_count, words_count,
cover_image, book_meta, synced
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
dbBook.book_id, dbBook.type, dbBook.author_id, dbBook.title, dbBook.hashed_title,
dbBook.sub_title ?? null, dbBook.hashed_sub_title, dbBook.summary, dbBook.serie_id ?? null,
dbBook.desired_release_date ?? null, dbBook.desired_word_count ?? null, dbBook.words_count ?? null,
dbBook.cover_image ?? null, dbBook.book_meta ?? null, 0
]);
// Add to pending changes for sync
this.addPendingChange('erit_books', 'INSERT', dbBook.book_id, dbBook);
}
/**
* Delete a book
*/
deleteBook(bookId: string): void {
if (!this.db) throw new Error('Database not initialized');
this.db.run('DELETE FROM erit_books WHERE book_id = ?', [bookId]);
this.addPendingChange('erit_books', 'DELETE', bookId);
}
// ========== CHAPTER OPERATIONS ==========
/**
* Save or update a chapter
*/
saveChapter(chapter: ChapterProps, bookId: string, contentId?: string): void {
if (!this.db || !this.userId) throw new Error('Database not initialized');
const dbChapter = ChapterMapper.chapterToDb(chapter, bookId, this.userId, 0);
dbChapter.hashed_title = hash(dbChapter.title);
this.db.run(`
INSERT OR REPLACE INTO book_chapters (
chapter_id, book_id, author_id, title, hashed_title, words_count,
chapter_order, meta_chapter, synced
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
dbChapter.chapter_id, dbChapter.book_id, dbChapter.author_id, dbChapter.title,
dbChapter.hashed_title, dbChapter.words_count ?? null, dbChapter.chapter_order ?? null,
dbChapter.meta_chapter, 0
]);
// Save encrypted content
const dbContent = ChapterMapper.chapterContentToDb(
chapter.chapterContent,
contentId || crypto.randomUUID(),
chapter.chapterId,
this.userId,
0,
0
);
// Encrypt the content
dbContent.content = this.encryptField(dbContent.content);
this.db.run(`
INSERT OR REPLACE INTO book_chapter_content (
content_id, chapter_id, author_id, version, content, words_count,
meta_chapter_content, time_on_it, synced
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
dbContent.content_id, dbContent.chapter_id, dbContent.author_id, dbContent.version,
dbContent.content, dbContent.words_count, dbContent.meta_chapter_content,
dbContent.time_on_it, 0
]);
this.addPendingChange('book_chapters', 'INSERT', chapter.chapterId, dbChapter);
}
// ========== CHARACTER OPERATIONS ==========
/**
* Get all characters for a book
*/
getCharacters(bookId: string): CharacterProps[] {
if (!this.db) throw new Error('Database not initialized');
const characterRows = this.db.all('SELECT * FROM book_characters WHERE book_id = ?', [bookId]) as unknown as CharacterMapper.DBCharacter[];
return characterRows.map(charRow => {
const attrRows = this.db!.all('SELECT * FROM book_characters_attributes WHERE character_id = ?', [charRow.character_id]) as unknown as CharacterMapper.DBCharacterAttribute[];
return CharacterMapper.dbToCharacter(charRow, attrRows);
});
}
/**
* Save or update a character
*/
saveCharacter(character: CharacterProps, bookId: string): void {
if (!this.db || !this.userId) throw new Error('Database not initialized');
const dbCharacter = CharacterMapper.characterToDb(character, bookId, this.userId, 0);
this.db.run(`
INSERT OR REPLACE INTO book_characters (
character_id, book_id, user_id, first_name, last_name, category, title,
image, role, biography, history, char_meta, synced
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
dbCharacter.character_id, dbCharacter.book_id, dbCharacter.user_id,
dbCharacter.first_name, dbCharacter.last_name ?? null, dbCharacter.category,
dbCharacter.title ?? null, dbCharacter.image ?? null, dbCharacter.role ?? null, dbCharacter.biography ?? null,
dbCharacter.history ?? null, dbCharacter.char_meta, 0
]);
// Delete old attributes and insert new ones
this.db.run('DELETE FROM book_characters_attributes WHERE character_id = ?', [dbCharacter.character_id]);
const attributes = CharacterMapper.characterAttributesToDb(character, this.userId, 0);
for (const attr of attributes) {
this.db.run(`
INSERT INTO book_characters_attributes (
attr_id, character_id, user_id, attribute_name, attribute_value, attr_meta, synced
) VALUES (?, ?, ?, ?, ?, ?, ?)
`, [
attr.attr_id, attr.character_id, attr.user_id, attr.attribute_name,
attr.attribute_value, attr.attr_meta, 0
]);
}
this.addPendingChange('book_characters', 'INSERT', dbCharacter.character_id, dbCharacter);
}
// ========== AI CONVERSATION OPERATIONS ==========
/**
* Get all conversations for a book
*/
getConversations(bookId: string): Conversation[] {
if (!this.db) throw new Error('Database not initialized');
const convoRows = this.db.all('SELECT * FROM ai_conversations WHERE book_id = ? ORDER BY start_date DESC', [bookId]) as unknown as AIMapper.DBConversation[];
return convoRows.map(convoRow => {
const messageRows = this.db!.all('SELECT * FROM ai_messages_history WHERE conversation_id = ? ORDER BY message_date ASC', [convoRow.conversation_id]) as unknown as AIMapper.DBMessage[];
// Decrypt messages
messageRows.forEach(msg => {
try {
msg.message = this.decryptField(msg.message);
} catch (error) {
console.warn('Failed to decrypt AI message:', error);
}
});
return AIMapper.dbToConversation(convoRow, messageRows);
});
}
/**
* Save a conversation with messages
*/
saveConversation(conversation: Conversation, bookId: string): void {
if (!this.db || !this.userId) throw new Error('Database not initialized');
const dbConvo = AIMapper.conversationToDb(conversation, bookId, this.userId, 0);
this.db.run(`
INSERT OR REPLACE INTO ai_conversations (
conversation_id, book_id, mode, title, start_date, status, user_id, summary, convo_meta, synced
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
dbConvo.conversation_id, dbConvo.book_id, dbConvo.mode, dbConvo.title,
dbConvo.start_date, dbConvo.status, dbConvo.user_id, dbConvo.summary ?? null,
dbConvo.convo_meta, 0
]);
// Save encrypted messages
for (const message of conversation.messages) {
const dbMessage = AIMapper.messageToDb(message, conversation.id, 0);
// Encrypt the message content
dbMessage.message = this.encryptField(dbMessage.message);
this.db.run(`
INSERT OR REPLACE INTO ai_messages_history (
message_id, conversation_id, role, message, message_date, meta_message, synced
) VALUES (?, ?, ?, ?, ?, ?, ?)
`, [
dbMessage.message_id, dbMessage.conversation_id, dbMessage.role,
dbMessage.message, dbMessage.message_date, dbMessage.meta_message, 0
]);
}
this.addPendingChange('ai_conversations', 'INSERT', dbConvo.conversation_id, dbConvo);
}
// ========== SYNC OPERATIONS ==========
/**
* Add a pending change for sync
*/
private addPendingChange(tableName: string, operation: string, recordId: string, data?: any): void {
if (!this.db) return;
this.db.run(`
INSERT INTO _pending_changes (table_name, operation, record_id, data, created_at)
VALUES (?, ?, ?, ?, ?)
`, [tableName, operation, recordId, data ? JSON.stringify(data) : null, Date.now()]);
// Update sync metadata
this.db.run(`
UPDATE _sync_metadata
SET pending_changes = pending_changes + 1
WHERE table_name = ?
`, [tableName]);
}
/**
* Get pending changes for sync
*/
getPendingChanges(limit: number = 100): any[] {
if (!this.db) throw new Error('Database not initialized');
return this.db.all(`
SELECT * FROM _pending_changes
ORDER BY created_at ASC
LIMIT ?
`, [limit]) as any[];
}
/**
* Mark changes as synced
*/
markChangesSynced(changeIds: number[]): void {
if (!this.db || changeIds.length === 0) return;
const placeholders = changeIds.map(() => '?').join(',');
this.db.run(`DELETE FROM _pending_changes WHERE id IN (${placeholders})`, changeIds);
}
/**
* Update last sync time for a table
*/
updateLastSync(tableName: string): void {
if (!this.db) return;
this.db.run(`
UPDATE _sync_metadata
SET last_sync_at = ?, pending_changes = 0
WHERE table_name = ?
`, [Date.now(), tableName]);
}
/**
* Get sync status
*/
getSyncStatus(): { table: string; lastSync: number; pending: number }[] {
if (!this.db) throw new Error('Database not initialized');
const rows = this.db.all('SELECT * FROM _sync_metadata', []) as unknown as any[];
return rows.map(row => ({
table: row.table_name as string,
lastSync: row.last_sync_at as number,
pending: row.pending_changes as number
}));
getUserId(): string | null {
return this.userId;
}
}

View File

@@ -1,21 +1,14 @@
import crypto from 'crypto';
/**
* Encryption utilities using AES-256-GCM for local database encryption
* Each user has a unique encryption key derived from their userId and a master secret
* Encryption utilities using AES-256-CBC for local database encryption
* EXACTEMENT comme dans Fastify System.ts
*/
const ALGORITHM = 'aes-256-gcm';
const ALGORITHM = 'aes-256-cbc';
const KEY_LENGTH = 32; // 256 bits
const IV_LENGTH = 16; // 128 bits
const SALT_LENGTH = 64;
const TAG_LENGTH = 16;
export interface EncryptedData {
encryptedData: string;
iv: string;
authTag: string;
}
/**
* Generate a unique encryption key for a user
@@ -54,77 +47,34 @@ function extractKeyFromStored(storedKey: string): Buffer {
}
/**
* Encrypt sensitive data using AES-256-GCM
* @param data - Plain text data to encrypt
* @param userKey - User's encryption key (base64)
* @returns Encrypted data with IV and auth tag
*/
export function encrypt(data: string, userKey: string): EncryptedData {
try {
const key = extractKeyFromStored(userKey);
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return {
encryptedData: encrypted,
iv: iv.toString('hex'),
authTag: authTag.toString('hex')
};
} catch (error) {
throw new Error(`Encryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Decrypt data encrypted with AES-256-GCM
* @param encryptedData - Encrypted data object
* @param userKey - User's encryption key (base64)
* @returns Decrypted plain text
*/
export function decrypt(encryptedData: EncryptedData, userKey: string): string {
try {
const key = extractKeyFromStored(userKey);
const iv = Buffer.from(encryptedData.iv, 'hex');
const authTag = Buffer.from(encryptedData.authTag, 'hex');
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encryptedData.encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
} catch (error) {
throw new Error(`Decryption failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Encrypt an object by converting it to JSON first
* @param obj - Object to encrypt
* Encrypt data with user key - EXACTEMENT comme Fastify
* @param data - Data to encrypt
* @param userKey - User's encryption key
* @returns Encrypted data
* @returns Encrypted string with format "iv:encryptedData"
*/
export function encryptObject<T>(obj: T, userKey: string): EncryptedData {
const jsonString = JSON.stringify(obj);
return encrypt(jsonString, userKey);
export function encryptDataWithUserKey(data: string, userKey: string): string {
const key = extractKeyFromStored(userKey);
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
let encryptedData = cipher.update(data, 'utf8', 'hex');
encryptedData += cipher.final('hex');
return iv.toString('hex') + ':' + encryptedData;
}
/**
* Decrypt and parse an encrypted object
* @param encryptedData - Encrypted data object
* Decrypt data with user key - EXACTEMENT comme Fastify
* @param encryptedData - Encrypted string with format "iv:encryptedData"
* @param userKey - User's encryption key
* @returns Decrypted and parsed object
* @returns Decrypted data
*/
export function decryptObject<T>(encryptedData: EncryptedData, userKey: string): T {
const decrypted = decrypt(encryptedData, userKey);
return JSON.parse(decrypted) as T;
export function decryptDataWithUserKey(encryptedData: string, userKey: string): string {
const [ivHex, encryptedHex] = encryptedData.split(':');
const iv = Buffer.from(ivHex, 'hex');
const key = extractKeyFromStored(userKey);
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
let decryptedData = decipher.update(encryptedHex, 'hex', 'utf8');
decryptedData += decipher.final('utf8');
return decryptedData || '';
}
/**
@@ -132,6 +82,18 @@ export function decryptObject<T>(encryptedData: EncryptedData, userKey: string):
* @param data - Data to hash
* @returns Hex encoded hash
*/
export function hash(data: string): string {
return crypto.createHash('sha256').update(data).digest('hex');
export function hashElement(data: string): string {
return crypto.createHash('sha256').update(data.toLowerCase().trim()).digest('hex');
}
// Pour compatibilité avec l'ancien code
export const encrypt = encryptDataWithUserKey;
export const decrypt = decryptDataWithUserKey;
export const hash = hashElement;
// Interface pour compatibilité (pas utilisée avec AES-CBC)
export interface EncryptedData {
encryptedData: string;
iv: string;
authTag: string;
}

View File

@@ -1,120 +0,0 @@
/**
* 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

@@ -1,406 +0,0 @@
/**
* 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

@@ -1,174 +0,0 @@
/**
* 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

@@ -1,185 +0,0 @@
/**
* 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

@@ -1,153 +0,0 @@
/**
* 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

@@ -1,222 +0,0 @@
/**
* 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;
}

View File

@@ -0,0 +1,728 @@
import {Database, QueryResult, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import System from "@/electron/database/System";
export interface BookQuery extends Record<string, SQLiteValue> {
book_id: string;
type: string;
author_id: string;
title: string;
hashed_title: string;
sub_title: string | null;
hashed_sub_title: string | null;
summary: string | null;
serie_id: number | null;
desired_release_date: string | null;
desired_word_count: number | null;
words_count: number | null;
cover_image: string | null;
book_meta: string | null;
}
export interface GuideLineQuery extends Record<string, SQLiteValue> {
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;
}
export interface PlotPointQuery extends Record<string, SQLiteValue> {
plot_point_id: string;
title: string;
summary: string;
linked_incident_id: string | null;
meta_plot: string;
}
export interface IncidentQuery extends Record<string, SQLiteValue> {
incident_id: string;
title: string;
summary: string;
meta_incident: string;
}
export interface IssueQuery extends Record<string, SQLiteValue> {
issue_id: string;
name: string;
meta_issue: string;
}
export interface ActQuery extends Record<string, SQLiteValue> {
act_index: number;
summary: string;
meta_acts: string;
}
export interface MetaBookQuery extends Record<string, SQLiteValue> {
book_meta: string;
}
export interface BookCoverQuery extends Record<string, SQLiteValue> {
cover_image: string;
book_meta: string;
}
export interface ChapterBookResult extends Record<string, SQLiteValue> {
title: string;
chapter_order: number;
meta_chapter: string;
content: string | null;
meta_chapter_content: string | null;
}
export interface WorldQuery extends Record<string, SQLiteValue> {
world_id: string;
world_name: string;
history: string | null;
politics: string | null;
economy: string | null;
religion: string | null;
languages: string | null;
meta_world: string;
element_id: string | null;
element_name: string | null;
element_description: string | null;
element_type: number | null;
meta_element: string | null;
}
export interface WorldElementValue {
id: string;
name: string;
description: string;
type: number;
meta: string;
}
export interface GuideLineAIQuery extends Record<string, SQLiteValue> {
user_id: string;
book_id: string;
global_resume: string | null;
themes: string | null;
verbe_tense: number | null;
narrative_type: number | null;
langue: number | null;
dialogue_type: number | null;
tone: string | null;
atmosphere: string | null;
current_resume: string | null;
meta: string;
}
export default class BookRepo {
public static fetchBooks(userId: string, lang: 'fr' | 'en'): BookQuery[] {
try {
const db: Database = System.getDb();
return db.all('SELECT book_id,type,author_id,`title`,`sub_title`,summary,serie_id,desired_release_date,desired_word_count,words_count,cover_image,`book_meta` FROM erit_books WHERE author_id = ? ORDER BY book_id DESC', [userId]) as BookQuery[];
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
throw new Error(lang === 'fr' ? 'Impossible de récupérer la liste des livres.' : 'Unable to retrieve book list.');
} else {
console.error(error);
throw new Error(lang === 'fr' ? 'Une erreur inconnue est survenue.' : 'An unknown error occurred.');
}
}
}
public static updateBookCover(bookId:string,coverImageName:string,userId:string, lang: 'fr' | 'en'):boolean{
try {
const db: Database = System.getDb();
const result:RunResult = db.run('UPDATE `erit_books` SET cover_image=? WHERE `book_id`=? AND author_id=?', [coverImageName, bookId, userId]);
return result.changes>0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour la couverture du livre.` : `Unable to update book cover.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static fetchBook(bookId: string, userId: string, lang: 'fr' | 'en'): BookQuery {
let result: BookQuery;
try {
const db: Database = System.getDb();
result = db.get('SELECT book_id, author_id, `title`, `summary`, `sub_title`, `cover_image`,`desired_release_date`, desired_word_count, `words_count`, book_meta FROM `erit_books` WHERE `book_id`=? AND author_id=?', [bookId, userId]) as BookQuery;
} catch (error: unknown) {
if (error instanceof Error) {
console.error(`DB Error: ${error.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer les informations du livre.` : `Unable to retrieve book information.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
if (!result) {
throw new Error(lang === 'fr' ? `Livre non trouvé.` : `Book not found.`);
}
return result;
}
public static verifyBookExist(hashedTitle:string,hashedSubTitle:string,userId:string, lang: 'fr' | 'en'):boolean{
try {
const db: Database = System.getDb();
const result:QueryResult|null = db.get('SELECT book_id FROM erit_books WHERE hashed_title=? AND author_id=? AND erit_books.hashed_sub_title=?', [hashedTitle,userId,hashedSubTitle]);
return result!==null;
} catch (err: unknown) {
if (err instanceof Error) {
console.error(`DB Error: ${err.message}`);
throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence du livre.` : `Unable to verify book existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static fetchGuideLine(userId: string, bookId: string, lang: 'fr' | 'en'): GuideLineQuery[] {
let result: GuideLineQuery[];
try {
const db: Database = System.getDb();
result = db.all('SELECT * FROM book_guide_line WHERE book_id=? AND user_id=?', [bookId, userId]) as GuideLineQuery[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer la ligne directrice.` : `Unable to retrieve guideline.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
return result;
}
public static fetchAllActs(userId: string, bookId: string, lang: 'fr' | 'en'): ActQuery[] {
try {
const db: Database = System.getDb();
return db.all('SELECT `act_index`,`summary`,`meta_acts` FROM `book_act_summaries` WHERE `book_id`=? AND `user_id`=?', [bookId, userId]) as ActQuery[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer les actes.` : `Unable to retrieve acts.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static fetchAllIncitentIncidents(userId:string,bookId:string, lang: 'fr' | 'en'):IncidentQuery[]{
try {
const db: Database = System.getDb();
return db.all('SELECT incident_id,`title`,`summary`,`meta_incident` FROM `book_incidents` WHERE `author_id`=? AND `book_id`=?', [userId, bookId]) as IncidentQuery[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer les incidents.` : `Unable to retrieve incidents.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static fetchAllPlotPoints(userId:string,bookId:string, lang: 'fr' | 'en'):PlotPointQuery[]{
try {
const db: Database = System.getDb();
return db.all('SELECT plot_point_id,`title`,`summary`,`linked_incident_id`,`meta_plot` FROM `book_plot_points` WHERE `author_id`=? AND `book_id`=?', [userId, bookId]) as PlotPointQuery[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer les points d'intrigue.` : `Unable to retrieve plot points.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static fetchIssuesFromBook(userId:string,bookId:string, lang: 'fr' | 'en'):IssueQuery[]{
try {
const db: Database = System.getDb();
return db.all('SELECT issue_id,`name`,`meta_issue` FROM `book_issues` WHERE `author_id`=? AND `book_id`=?', [userId, bookId]) as IssueQuery[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer les problématiques.` : `Unable to retrieve issues.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static insertBook(bookId: string, userId: string, encryptedTitle: string, hashedTitle: string, encryptedSubTitle: string, hashedSubTitle: string, encryptedSummary: string, type: string, serie: number, publicationDate: string, desiredWordCount: number, lang: 'fr' | 'en'): string {
let result:RunResult
try {
const db: Database = System.getDb();
result = db.run('INSERT INTO erit_books (book_id,type,author_id, title, hashed_title, sub_title, hashed_sub_title, summary, serie_id, desired_release_date, erit_books.desired_word_count) VALUES (?,?,?,?,?,?,?,?,?,?,?)', [bookId, type, userId, encryptedTitle, hashedTitle, encryptedSubTitle, hashedSubTitle, encryptedSummary, serie, publicationDate ? publicationDate : null, desiredWordCount]);
} catch (err: unknown) {
if (err instanceof Error) {
console.error(`DB Error: ${err.message}`);
throw new Error(lang === 'fr' ? `Impossible d'ajouter le livre.` : `Unable to add book.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
if (result.changes > 0) {
return bookId;
} else {
throw new Error(lang === 'fr' ? `Erreur lors de l'ajout du livre.` : `Error adding book.`);
}
}
public static fetchBookCover(userId:string,bookId:string, lang: 'fr' | 'en'):BookCoverQuery{
try {
const db: Database = System.getDb();
return db.get('SELECT cover_image, book_meta FROM erit_books WHERE author_id=? AND book_id=?', [userId, bookId]) as BookCoverQuery;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer la couverture du livre.` : `Unable to retrieve book cover.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static updateBookBasicInformation(userId: string, title: string, hashedTitle: string, subTitle: string, hashedSubTitle: string, summary: string, publicationDate: string, wordCount: number, bookId: string, bookMeta: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('UPDATE erit_books SET title=?, hashed_title=?, sub_title=?, hashed_sub_title=?, summary=?, serie_id=?, desired_release_date=?, desired_word_count=?, book_meta=? WHERE author_id=? AND book_id=?',
[title, hashedTitle, subTitle, hashedSubTitle, summary, 0, publicationDate ? System.dateToMySqlDate(publicationDate) : null, wordCount, bookMeta, userId, bookId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour les informations du livre.` : `Unable to update book information.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static updateGuideLine(userId: string, bookId: string, encryptedTone: string, encryptedAtmosphere: string, encryptedWritingStyle: string, encryptedThemes: string, encryptedSymbolism: string, encryptedMotifs: string, encryptedNarrativeVoice: string, encryptedPacing: string, encryptedKeyMessages: string, encryptedIntendedAudience: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('UPDATE book_guide_line SET tone=?, atmosphere=?, writing_style=?, themes=?, symbolism=?, motifs=?, narrative_voice=?, pacing=?, key_messages=? WHERE user_id=? AND book_id=?', [encryptedTone, encryptedAtmosphere, encryptedWritingStyle, encryptedThemes, encryptedSymbolism, encryptedMotifs, encryptedNarrativeVoice, encryptedPacing, encryptedKeyMessages, userId, bookId]);
if (result.changes > 0) {
return true;
} else {
const insert:RunResult = db.run('INSERT INTO book_guide_line (user_id, book_id, tone, atmosphere, writing_style, themes, symbolism, motifs, narrative_voice, pacing, intended_audience, key_messages) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)', [userId, bookId, encryptedTone, encryptedAtmosphere, encryptedWritingStyle, encryptedThemes, encryptedSymbolism, encryptedMotifs, encryptedNarrativeVoice, encryptedPacing, encryptedIntendedAudience, encryptedKeyMessages]);
return insert.changes > 0;
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour la ligne directrice.` : `Unable to update guideline.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static updateActSummary(userId: string, bookId: string, actId: number, summary: string, metaActs: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('UPDATE book_act_summaries SET summary=?, meta_acts=? WHERE user_id=? AND book_id=? AND act_sum_id=?', [summary, metaActs, userId, bookId, actId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour le résumé de l'acte.` : `Unable to update act summary.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static insertNewIncident(incidentId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, lang: 'fr' | 'en'): string {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('INSERT INTO book_incidents (incident_id,author_id, book_id, title, hashed_title) VALUES (?,?,?,?,?)', [incidentId, userId, bookId, encryptedName, hashedName]);
if (result.changes > 0) {
return incidentId;
} else {
throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout de l'élément déclencheur.` : `Error adding incident.`);
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible d'ajouter l'élément déclencheur.` : `Unable to add incident.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static deleteIncident(userId: string, bookId: string, incidentId: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('DELETE FROM book_incidents WHERE author_id=? AND book_id=? AND incident_id=?', [userId, bookId, incidentId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de supprimer l'élément déclencheur.` : `Unable to delete incident.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static insertNewPlotPoint(plotPointId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, incidentId: string, metaPlot: string, lang: 'fr' | 'en'): string {
try {
const db: Database = System.getDb();
const existingResult = db.get('SELECT plot_point_id FROM book_plot_points WHERE author_id=? AND book_id=? AND hashed_title=?', [userId, bookId, hashedName]);
if (existingResult !== null) {
throw new Error(lang === 'fr' ? `Ce point de l'intrigue existe déjà.` : `This plot point already exists.`);
}
const insertResult: RunResult = db.run('INSERT INTO book_plot_points (plot_point_id,title,hashed_title,author_id,book_id,linked_incident_id,meta_plot) VALUES (?,?,?,?,?,?,?)', [plotPointId, encryptedName, hashedName, userId, bookId, incidentId, metaPlot]);
if (insertResult.changes > 0) {
return plotPointId;
} else {
throw new Error(lang === 'fr' ? `Une erreur s'est produite lors de l'ajout du point d'intrigue.` : `Error adding plot point.`);
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible d'ajouter le point d'intrigue.` : `Unable to add plot point.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static deletePlotPoint(userId: string, plotNumId: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('DELETE FROM book_plot_points WHERE author_id=? AND plot_point_id=?', [userId, plotNumId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de supprimer le point d'intrigue.` : `Unable to delete plot point.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static insertNewIssue(issueId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, metaIssue: string, lang: 'fr' | 'en'): string {
try {
const db: Database = System.getDb();
const result = db.get('SELECT issue_id FROM book_issues WHERE hashed_issue_name=? AND book_id=? AND author_id=?', [hashedName, bookId, userId]);
if (result !== null) {
throw new Error(lang === 'fr' ? `La problématique existe déjà.` : `This issue already exists.`);
}
const insertResult: RunResult = db.run('INSERT INTO book_issues (issue_id,author_id, book_id, name, hashed_issue_name, meta_issue) VALUES (?,?,?,?,?,?)', [issueId, userId, bookId, encryptedName, hashedName, metaIssue]);
if (insertResult.changes > 0) {
return issueId;
} else {
throw new Error(lang === 'fr' ? `Erreur pendant l'ajout de la problématique.` : `Error adding issue.`);
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible d'ajouter la problématique.` : `Unable to add issue.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static deleteIssue(userId: string, issueId: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('DELETE FROM book_issues WHERE author_id=? AND issue_id=?', [userId, issueId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de supprimer la problématique.` : `Unable to delete issue.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static updateIncident(userId: string, bookId: string, incidentId: string, encryptedIncidentName: string, incidentHashedName: string, incidentSummary: string, meta: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('UPDATE book_incidents SET title=?, hashed_title=?, summary=?, meta_incident=? WHERE author_id=? AND book_id=? AND incident_id=?', [encryptedIncidentName, incidentHashedName, incidentSummary, meta, userId, bookId, incidentId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour l'incident.` : `Unable to update incident.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static updatePlotPoint(userId: string, bookId: string, plotPointId: string, encryptedPlotPointName: string, plotPointHashedName: string, plotPointSummary: string, meta: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('UPDATE book_plot_points SET title=?, hashed_title=?, summary=?, meta_plot=? WHERE author_id=? AND book_id=? AND plot_point_id=?', [encryptedPlotPointName, plotPointHashedName, plotPointSummary, meta, userId, bookId, plotPointId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour le point d'intrigue.` : `Unable to update plot point.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static checkWorldExist(userId:string,bookId:string,worldName:string, lang: 'fr' | 'en'):boolean{
try {
const db: Database = System.getDb();
const result = db.get('SELECT world_id FROM book_world WHERE author_id=? AND book_id=? AND hashed_name=?', [userId,bookId,worldName]);
return result !== null;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence du monde.` : `Unable to verify world existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static insertNewWorld(worldId: string, userId: string, bookId: string, encryptedName: string, hashedName: string, meta: string, lang: 'fr' | 'en'): string {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('INSERT INTO book_world (world_id,author_id, book_id, name, hashed_name, meta_world) VALUES (?,?,?,?,?,?)', [worldId, userId, bookId, encryptedName, hashedName, meta]);
if (result.changes > 0) {
return worldId;
} else {
throw new Error(lang === 'fr' ? `Erreur lors de l'ajout du monde.` : `Error adding world.`);
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible d'ajouter le monde.` : `Unable to add world.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static fetchWorlds(userId: string, bookId: string, lang: 'fr' | 'en'):WorldQuery[] {
try {
const db: Database = System.getDb();
return db.all('SELECT world.world_id AS world_id, world.name AS world_name,world.history, world.politics, world.economy, world.religion, world.languages, world.meta_world, element.element_id AS element_id,element.name AS element_name, element.description AS element_description, element.element_type, element.meta_element FROM `book_world` AS world LEFT JOIN book_world_elements AS element ON world.world_id=element.world_id WHERE world.author_id=? AND world.book_id=?', [userId, bookId]) as WorldQuery[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer les mondes.` : `Unable to retrieve worlds.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static updateWorld(userId: string, worldId: string, encryptName: string, hashedName: string, encryptHistory: string, encryptPolitics: string, encryptEconomy: string, encryptReligion: string, encryptLanguages: string, meta: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('UPDATE book_world SET name=?, hashed_name=?, history=?, politics=?, economy=?, religion=?, languages=?, meta_world=? WHERE author_id=? AND world_id=?', [encryptName, hashedName, encryptHistory, encryptPolitics, encryptEconomy, encryptReligion, encryptLanguages, meta, userId, worldId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour le monde.` : `Unable to update world.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static updateWorldElements(userId: string, elements: WorldElementValue[], lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
for (const element of elements) {
const result: RunResult = db.run('UPDATE book_world_elements SET name=?, description=?, element_type=?, meta_element=? WHERE user_id=? AND element_id=?', [element.name, element.description, element.type, element.meta, userId, element.id]);
if (result.changes <= 0) {
return false;
}
}
return true;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour les éléments du monde.` : `Unable to update world elements.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static checkElementExist(worldNumId: string, hashedName: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result = db.get('SELECT element_id FROM book_world_elements WHERE world_id=? AND original_name=?', [worldNumId, hashedName]);
return result !== null;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence de l'élément.` : `Unable to verify element existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static insertNewElement(userId: string, elementId: string, elementType: number, worldId: string, encryptedName: string, hashedName: string, meta: string, lang: 'fr' | 'en'): string {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('INSERT INTO book_world_elements (element_id,world_id,user_id, name, original_name, element_type, meta_element) VALUES (?,?,?,?,?,?,?)', [elementId, worldId, userId, encryptedName, hashedName, elementType, meta]);
if (result.changes > 0) {
return elementId;
} else {
throw new Error(lang === 'fr' ? `Erreur lors de l'ajout de l'élément.` : `Error adding element.`);
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible d'ajouter l'élément.` : `Unable to add element.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static deleteElement(userId: string, elementId: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('DELETE FROM book_world_elements WHERE user_id=? AND element_id=?', [userId, elementId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de supprimer l'élément.` : `Unable to delete element.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static deleteBook(userId: string, bookId: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('DELETE FROM erit_books WHERE author_id=? AND book_id=?', [userId,bookId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de supprimer le livre.` : `Unable to delete book.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static insertAIGuideLine(userId: string, bookId: string, narrativeType: number, dialogueType: number, encryptedPlotSummary: string, encryptedToneAtmosphere: string, verbTense: number, language: number, encryptedThemes: string, metaGuideLine: string, lang: 'fr' | 'en'): boolean {
try {
const db: Database = System.getDb();
let result: RunResult = db.run('UPDATE book_ai_guide_line SET narrative_type=?, dialogue_type=?, global_resume=?, atmosphere=?, verbe_tense=?, langue=?, themes=?, meta=? WHERE user_id=? AND book_id=?', [narrativeType ? narrativeType : null, dialogueType ? dialogueType : null, encryptedPlotSummary, encryptedToneAtmosphere, verbTense ? verbTense : null, language ? language : null, encryptedThemes, metaGuideLine, userId, bookId]);
if (result.changes > 0) {
return true;
} else {
result = db.run('INSERT INTO book_ai_guide_line (user_id, book_id, global_resume, themes, verbe_tense, narrative_type, langue, dialogue_type, tone, atmosphere, current_resume, meta) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)', [userId, bookId, encryptedPlotSummary, encryptedThemes, verbTense ? verbTense : null, narrativeType ? narrativeType : null, language ? language : null, dialogueType ? dialogueType : null, encryptedToneAtmosphere, encryptedToneAtmosphere, encryptedPlotSummary, metaGuideLine]);
return result.changes > 0;
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible d'insérer la ligne directrice IA.` : `Unable to insert AI guideline.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static fetchGuideLineAI(userId: string, bookId: string, lang: 'fr' | 'en'): GuideLineAIQuery {
try {
const db: Database = System.getDb();
const result = db.get('SELECT narrative_type, dialogue_type, global_resume, atmosphere, verbe_tense, langue, themes, current_resume, meta FROM book_ai_guide_line WHERE user_id=? AND book_id=?', [userId, bookId]) as GuideLineAIQuery | null;
if (!result) {
throw new Error(lang === 'fr' ? `Ligne directrice IA non trouvée.` : `AI guideline not found.`);
}
return result;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer la ligne directrice IA.` : `Unable to retrieve AI guideline.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static insertActSummary(actSummaryId: string, userId: string, bookId: string, actId: number, actSummary: string, meta: string, lang: 'fr' | 'en'): string {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('INSERT INTO book_act_summaries (act_sum_id, book_id, user_id, act_index, summary, meta_acts) VALUES (?,?,?,?,?,?)', [actSummaryId, bookId, userId, actId, actSummary, meta]);
if (result.changes > 0) {
return actSummaryId;
} else {
throw new Error(lang === 'fr' ? `Erreur lors de l'ajout du résumé de l'acte.` : `Error adding act summary.`);
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible d'ajouter le résumé de l'acte.` : `Unable to add act summary.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static fetchCompleteBookChapters(id: string, lang: 'fr' | 'en'): ChapterBookResult[] {
try {
const db: Database = System.getDb();
const result = db.all('SELECT title, chapter_order, meta_chapter, content.content, content.meta_chapter_content FROM `book_chapters` AS chapter LEFT JOIN book_chapter_content AS content ON chapter.chapter_id = content.chapter_id AND content.version = (SELECT MAX(version) FROM book_chapter_content WHERE chapter_id = chapter.chapter_id AND version > 1) WHERE chapter.book_id = ? ORDER BY chapter.chapter_order', [id]) as ChapterBookResult[];
if (result.length === 0) {
throw new Error(lang === 'fr' ? `Aucun chapitre trouvé.` : `No chapters found.`);
}
return result;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer les chapitres.` : `Unable to retrieve chapters.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
}

View File

@@ -0,0 +1,425 @@
import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import System from "../System";
export interface ChapterContentQueryResult extends Record<string, SQLiteValue>{
chapter_id: string;
version: number;
content: string;
words_count: number;
title: string;
content_meta: string;
meta_chapter: string;
chapter_order: number;
}
export interface ContentQueryResult extends Record<string, SQLiteValue> {
content: string,
meta_chapter_content: string,
}
export interface ChapterQueryResult extends Record<string, SQLiteValue>{
chapter_id: string;
title: string;
chapter_order:number;
meta_chapter: string;
}
export interface ActChapterQuery extends Record<string, SQLiteValue>{
chapter_info_id: number;
chapter_id: string;
title: string;
chapter_order: number;
meta_chapter: string;
act_id: number;
incident_id: string | null;
plot_point_id: string | null;
summary: string;
goal: string;
meta_chapter_info: string;
}
export interface CompanionContentQueryResult extends Record<string, SQLiteValue>{
version: number;
content: string;
words_count: number;
meta_chapter_content: string;
}
export interface ChapterStoryQueryResult extends Record<string, SQLiteValue>{
chapter_info_id: number;
act_id: number;
summary:string;
meta_acts:string;
chapter_summary: string;
chapter_goal: string;
meta_chapter_info: string;
incident_id: number;
incident_title: string;
incident_summary: string;
meta_incident: string;
plot_point_id: number;
plot_title: string;
plot_summary: string;
meta_plot: string;
}
export interface LastChapterResult extends Record<string, SQLiteValue>{
chapter_id: string;
version: number;
}
export default class ChapterRepo{
public static checkNameDuplication(userId:string,bookId:string,hashedTitle:string, lang: 'fr' | 'en' = 'fr'):boolean{
try {
const db: Database = System.getDb();
const result = db.get('SELECT chapter_id FROM book_chapters WHERE author_id=? AND book_id=? AND hashed_title=?', [userId,bookId,hashedTitle]);
return result !== null;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de vérifier la duplication du nom.` : `Unable to verify name duplication.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static insertChapter(chapterId: string, userId: string, bookId: string, title: string, hashedTitle: string, wordsCount: number, chapterOrder: number, meta: string, lang: 'fr' | 'en' = 'fr'): string {
let result: RunResult;
try {
const db: Database = System.getDb();
result = db.run('INSERT INTO book_chapters (chapter_id,author_id, book_id, title, hashed_title, words_count, chapter_order, meta_chapter) VALUES (?,?,?,?,?,?,?,?)', [chapterId, userId, bookId, title, hashedTitle, wordsCount, chapterOrder, meta]);
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible d'ajouter le chapitre.` : `Unable to add chapter.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
if (result.changes > 0) {
return chapterId;
} else {
throw new Error(lang === 'fr' ? `Une erreur s'est passé lors de l'ajout du chapitre.` : `Error adding chapter.`);
}
}
public static fetchWholeChapter(userId: string, chapterId: string, version: number, lang: 'fr' | 'en' = 'fr'): ChapterContentQueryResult {
let result: ChapterContentQueryResult | null;
try {
const db: Database = System.getDb();
const query: string = 'SELECT chapter.chapter_id as chapter_id,chapter.title as title, chapter.chapter_order, chapter.words_count, chapter.meta_chapter, content.content AS content, content.version as version, content.meta_chapter_content as content_meta FROM book_chapters AS chapter LEFT JOIN book_chapter_content AS content ON content.chapter_id = chapter.chapter_id AND content.version = ? WHERE chapter.chapter_id = ? AND chapter.author_id = ?';
result = db.get(query, [version, chapterId, userId]) as ChapterContentQueryResult | null;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer le chapitre.` : `Unable to retrieve chapter.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
if (!result) {
throw new Error(lang === 'fr' ? `Aucun chapitre trouvé avec cet ID.` : `No chapter found with this ID.`);
}
return result;
}
public static fetchLastChapterContent(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterContentQueryResult[] {
try {
const db: Database = System.getDb();
const query: string = `SELECT book_chapters.chapter_id as chapter_id, COALESCE(book_chapter_content.version, 2) AS version, COALESCE(book_chapter_content.content, '') AS content, COALESCE(book_chapter_content.words_count, 0) AS words_count, book_chapters.title, book_chapters.meta_chapter, book_chapters.chapter_order FROM book_chapters LEFT JOIN book_chapter_content ON book_chapters.chapter_id = book_chapter_content.chapter_id WHERE book_chapters.author_id = ? AND book_chapters.book_id = ? ORDER BY book_chapters.chapter_order DESC, book_chapter_content.version DESC LIMIT 1`;
return db.all(query, [userId, bookId]) as ChapterContentQueryResult[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer le dernier chapitre.` : `Unable to retrieve last chapter.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static fetchAllChapterForActs(userId:string,bookId:string, lang: 'fr' | 'en' = 'fr'):ActChapterQuery[]{
try {
const db: Database = System.getDb();
return db.all('SELECT ci.chapter_info_id AS chapter_info_id, ci.chapter_id AS chapter_id, chapter.title, chapter.chapter_order, chapter.meta_chapter, ci.act_id, ci.incident_id AS incident_id, ci.plot_point_id AS plot_point_id, ci.summary, ci.goal, ci.meta_chapter_info FROM `book_chapter_infos` AS ci INNER JOIN book_chapters AS chapter ON chapter.chapter_id = ci.chapter_id WHERE ci.book_id = ? AND ci.author_id = ?', [bookId, userId]) as ActChapterQuery[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer les chapitres pour les actes.` : `Unable to retrieve chapters for acts.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static fetchAllChapterFromABook(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): ChapterQueryResult[] {
try {
const db: Database = System.getDb();
return db.all('SELECT chapter_id as chapter_id, title, chapter_order, meta_chapter FROM book_chapters WHERE book_id=? AND author_id=? ORDER BY chapter_order', [bookId, userId]) as ChapterQueryResult[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer les chapitres.` : `Unable to retrieve chapters.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static deleteChapter(userId: string, chapterId: string, lang: 'fr' | 'en' = 'fr'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('DELETE FROM book_chapters WHERE author_id=? AND chapter_id=?', [userId, chapterId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de supprimer le chapitre.` : `Unable to delete chapter.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static insertChapterInformation(chapterInfoId: string, userId: string, chapterId: string, actId: number, bookId: string, plotId: string | null, incidentId: string | null, lang: 'fr' | 'en' = 'fr'): string {
let existResult;
let result: RunResult;
try {
const db: Database = System.getDb();
existResult = db.get('SELECT chapter_info_id FROM book_chapter_infos WHERE chapter_id=? AND act_id=? AND book_id=? AND plot_point_id=? AND incident_id=? AND author_id=?', [chapterId, actId, bookId, plotId, incidentId, userId]);
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de vérifier l'existence de l'information du chapitre.` : `Unable to verify chapter information existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
if (existResult !== null) {
throw new Error(lang === 'fr' ? `Le chapitre est déjà lié.` : `Chapter is already linked.`);
}
try {
const db: Database = System.getDb();
result = db.run('INSERT INTO book_chapter_infos (chapter_info_id,chapter_id, act_id, book_id, author_id, incident_id, plot_point_id, summary, goal, meta_chapter_info) VALUES (?,?,?,?,?,?,?,?,?,?)', [chapterInfoId, chapterId, actId, bookId, userId, incidentId, plotId, '', '', '']);
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible d'ajouter l'information du chapitre.` : `Unable to add chapter information.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
if (result.changes > 0) {
return chapterInfoId;
} else {
throw new Error(lang === 'fr' ? `Une erreur s'est produite pendant la liaison du chapitre.` : `Error linking chapter.`);
}
}
public static updateChapter(userId: string, chapterId: string, encryptedTitle: string, hashTitle: string, chapterOrder: number, meta: string, lang: 'fr' | 'en' = 'fr'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('UPDATE book_chapters SET title=?, hashed_title=?, chapter_order=?, meta_chapter=? WHERE author_id=? AND chapter_id=?', [encryptedTitle, hashTitle, chapterOrder, meta, userId, chapterId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour le chapitre.` : `Unable to update chapter.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static updateChapterInfos(userId: string, chapterId: string, actId: number, bookId: string, incidentId: string | null, plotId: string | null, summary: string, goal: string | null, meta: string, lang: 'fr' | 'en' = 'fr'): boolean {
try {
const db: Database = System.getDb();
let sql: string = `UPDATE book_chapter_infos
SET summary=?,
goal=?,
meta_chapter_info=?
WHERE chapter_id = ?
AND act_id = ?
AND book_id = ?`;
const params: any[] = [summary, goal, meta, chapterId, actId, bookId];
if (incidentId) {
sql += ` AND incident_id=?`;
params.push(incidentId);
} else {
sql += ` AND incident_id IS NULL`;
}
if (plotId) {
sql += ` AND plot_point_id=?`;
params.push(plotId);
} else {
sql += ` AND plot_point_id IS NULL`;
}
sql += ` AND author_id=?`;
params.push(userId);
const result: RunResult = db.run(sql, params);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour les informations du chapitre.` : `Unable to update chapter information.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static updateChapterContent(userId: string, chapterId: string, version: number, encryptContent: string, wordsCount: number, meta: string, lang: 'fr' | 'en' = 'fr'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('UPDATE book_chapter_content SET content=?, meta_chapter_content=?, words_count=? WHERE chapter_id=? AND author_id=? AND version=?', [encryptContent, meta, wordsCount, chapterId, userId, version]);
if (result.changes > 0) {
return true;
} else {
const contentId:string = System.createUniqueId();
const insertResult: RunResult = db.run('INSERT INTO book_chapter_content (content_id,chapter_id, author_id, version, content, words_count, meta_chapter_content) VALUES (?,?,?,?,?,?,?)', [contentId, chapterId, userId, version, encryptContent, wordsCount, meta]);
return insertResult.changes > 0;
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de mettre à jour le contenu du chapitre.` : `Unable to update chapter content.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static fetchCompanionContent(userId: string, chapterIdNum: string, versionNum: number, lang: 'fr' | 'en' = 'fr'): CompanionContentQueryResult[] {
try {
const db: Database = System.getDb();
return db.all('SELECT version, content, words_count, meta_chapter_content FROM book_chapter_content WHERE author_id=? AND chapter_id=? AND version=?', [userId, chapterIdNum, versionNum]) as CompanionContentQueryResult[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer le contenu compagnon.` : `Unable to retrieve companion content.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static fetchLastChapter(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): LastChapterResult | null {
try {
const db: Database = System.getDb();
const result = db.get('SELECT chapter_id as chapter_id,version FROM user_last_chapter WHERE user_id=? AND book_id=?', [userId, bookId]) as LastChapterResult | null;
return result;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer le dernier chapitre ouvert.` : `Unable to retrieve last opened chapter.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static updateLastChapterRecord(userId: string, bookId: string, chapterId: string, version: number, lang: 'fr' | 'en' = 'fr'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('UPDATE user_last_chapter SET chapter_id=?, version=? WHERE user_id=? AND book_id=?', [chapterId, version, userId, bookId]);
if (result.changes > 0) {
return true;
} else {
const insertResult: RunResult = db.run('INSERT INTO user_last_chapter (user_id, book_id, chapter_id, version) VALUES (?,?,?,?)', [userId, bookId, chapterId, version]);
return insertResult.changes > 0;
}
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible d'enregistrer le dernier chapitre.` : `Unable to save last chapter.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
public static fetchChapterStory(userId: string, chapterId: string, lang: 'fr' | 'en' = 'fr'):ChapterStoryQueryResult[] {
try {
const db: Database = System.getDb();
return db.all('SELECT chapter_info_id,chapter.act_id,act_sum.summary,act_sum.meta_acts,chapter.summary AS chapter_summary,chapter.goal AS chapter_goal,meta_chapter_info,chapter.incident_id,incident.title AS incident_title, incident.summary AS incident_summary,incident.meta_incident,chapter.plot_point_id,plot.title AS plot_title,plot.summary AS plot_summary,plot.meta_plot FROM `book_chapter_infos` AS chapter LEFT JOIN book_incidents AS incident ON chapter.incident_id=incident.incident_id LEFT JOIN book_plot_points AS plot ON chapter.plot_point_id=plot.plot_point_id LEFT JOIN book_act_summaries AS act_sum ON chapter.act_id=act_sum.act_sum_id AND chapter.book_id=act_sum.book_id WHERE chapter.chapter_id=? AND chapter.author_id=?', [chapterId, userId]) as ChapterStoryQueryResult[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer l'histoire du chapitre.` : `Unable to retrieve chapter story.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static fetchChapterContentByChapterOrder(userId: string, chapterOrder: number, bookId: string, lang: 'fr' | 'en' = 'fr'): ContentQueryResult {
let result: ContentQueryResult | null;
try {
const db: Database = System.getDb();
result = db.get('SELECT content.content,content.meta_chapter_content FROM `book_chapters` as chapter INNER JOIN book_chapter_content AS content ON chapter.chapter_id=content.chapter_id WHERE chapter.chapter_order=? AND content.version=2 AND chapter.book_id=? AND chapter.author_id=?', [chapterOrder, bookId, userId]) as ContentQueryResult | null;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer le contenu du chapitre.` : `Unable to retrieve chapter content.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
if (!result) {
throw new Error(lang === 'fr' ? `Aucun chapitre trouvé avec cet ordre.` : `No chapter found with this order.`);
}
return result;
}
static fetchChapterContentByVersion(userId: string, chapterid: string, version: number, lang: 'fr' | 'en' = 'fr'): ContentQueryResult {
let result: ContentQueryResult | null;
try {
const db: Database = System.getDb();
result = db.get('SELECT content, meta_chapter_content FROM book_chapter_content WHERE author_id=? AND chapter_id=? AND version=?', [userId, chapterid, version]) as ContentQueryResult | null;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de récupérer le contenu du chapitre.` : `Unable to retrieve chapter content.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
if (!result) {
throw new Error(lang === 'fr' ? `Aucun chapitre trouvé avec cette version.` : `No chapter found with this version.`);
}
return result;
}
static deleteChapterInformation(userId: string, chapterInfoId: string, lang: 'fr' | 'en' = 'fr'): boolean {
try {
const db: Database = System.getDb();
const result: RunResult = db.run('DELETE FROM book_chapter_infos WHERE chapter_info_id=? AND author_id=?', [chapterInfoId, userId]);
return result.changes > 0;
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
throw new Error(lang === 'fr' ? `Impossible de supprimer les informations du chapitre.` : `Unable to delete chapter information.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
}