Remove all database mappers and README file
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
728
electron/database/repositories/book.repository.ts
Normal file
728
electron/database/repositories/book.repository.ts
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
425
electron/database/repositories/chapter.repository.ts
Normal file
425
electron/database/repositories/chapter.repository.ts
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user