Remove all database mappers and README file
This commit is contained in:
116
README.md
116
README.md
@@ -1,116 +0,0 @@
|
|||||||
# EritorsScribe - Electron + Next.js
|
|
||||||
|
|
||||||
Application Electron avec Next.js et TypeScript.
|
|
||||||
|
|
||||||
## Structure du projet
|
|
||||||
|
|
||||||
```
|
|
||||||
eritorsscribe/
|
|
||||||
├── electron/ # Code Electron (main process)
|
|
||||||
│ ├── main.ts # Point d'entrée principal
|
|
||||||
│ └── preload.ts # Script preload (bridge sécurisé)
|
|
||||||
├── src/ # Mettez vos fichiers Next.js ici (app/, pages/, components/, etc.)
|
|
||||||
├── dist/ # Fichiers compilés Electron
|
|
||||||
├── out/ # Export statique Next.js
|
|
||||||
├── build/ # Configuration electron-builder
|
|
||||||
└── release/ # Binaires packagés
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Les dépendances sont déjà installées. Si besoin:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Développement
|
|
||||||
|
|
||||||
1. Mettez vos fichiers Next.js dans le dossier `src/` (créez `src/app/` ou `src/pages/` selon votre structure Next.js)
|
|
||||||
|
|
||||||
2. Lancez le mode développement:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Cela va:
|
|
||||||
- Démarrer Next.js sur http://localhost:3000
|
|
||||||
- Lancer Electron qui charge cette URL
|
|
||||||
- Recharger automatiquement au changement
|
|
||||||
|
|
||||||
## Scripts disponibles
|
|
||||||
|
|
||||||
- `npm run dev` - Développement (Next.js + Electron)
|
|
||||||
- `npm run dev:next` - Next.js uniquement
|
|
||||||
- `npm run dev:electron` - Electron uniquement
|
|
||||||
- `npm run build` - Build complet (Next.js + Electron)
|
|
||||||
- `npm run start` - Lancer l'app compilée
|
|
||||||
- `npm run package:mac` - Packager pour macOS
|
|
||||||
- `npm run package:win` - Packager pour Windows
|
|
||||||
- `npm run package:linux` - Packager pour Linux
|
|
||||||
- `npm run package` - Packager pour toutes les plateformes
|
|
||||||
|
|
||||||
## Build de production
|
|
||||||
|
|
||||||
1. Compilez tout:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Packagez pour votre plateforme:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# macOS
|
|
||||||
npm run package:mac
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
npm run package:win
|
|
||||||
|
|
||||||
# Linux
|
|
||||||
npm run package:linux
|
|
||||||
|
|
||||||
# Toutes les plateformes
|
|
||||||
npm run package
|
|
||||||
```
|
|
||||||
|
|
||||||
Les binaires seront dans le dossier `release/`.
|
|
||||||
|
|
||||||
## Versions installées
|
|
||||||
|
|
||||||
- Electron: 39.x (dernière version stable)
|
|
||||||
- Next.js: 16.x
|
|
||||||
- React: 19.x
|
|
||||||
- TypeScript: 5.9.x
|
|
||||||
- electron-builder: 26.x
|
|
||||||
|
|
||||||
## Configuration Next.js
|
|
||||||
|
|
||||||
Le fichier `next.config.ts` est configuré avec:
|
|
||||||
- `output: 'export'` - Export statique pour Electron
|
|
||||||
- `images.unoptimized: true` - Images non optimisées
|
|
||||||
- `trailingSlash: true` - Compatibilité Electron
|
|
||||||
|
|
||||||
## Sécurité
|
|
||||||
|
|
||||||
Le preload script utilise:
|
|
||||||
- `contextIsolation: true`
|
|
||||||
- `nodeIntegration: false`
|
|
||||||
- `sandbox: true`
|
|
||||||
|
|
||||||
Pour exposer des APIs au renderer, modifiez `electron/preload.ts`.
|
|
||||||
|
|
||||||
## Multi-plateforme
|
|
||||||
|
|
||||||
- macOS: Build sur Mac (requis pour signing)
|
|
||||||
- Windows: Build sur n'importe quelle plateforme
|
|
||||||
- Linux: Build sur n'importe quelle plateforme
|
|
||||||
|
|
||||||
## Prochaines étapes
|
|
||||||
|
|
||||||
1. Créez votre structure Next.js dans `src/`
|
|
||||||
2. Ajoutez vos pages dans `src/app/` ou `src/pages/`
|
|
||||||
3. Testez avec `npm run dev`
|
|
||||||
4. Personnalisez `electron/main.ts` selon vos besoins
|
|
||||||
5. Ajoutez des APIs dans `electron/preload.ts` si nécessaire
|
|
||||||
@@ -2,31 +2,14 @@ import sqlite3 from 'node-sqlite3-wasm';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { app } from 'electron';
|
import { app } from 'electron';
|
||||||
import { initializeSchema } from './schema.js';
|
import { initializeSchema } from './schema.js';
|
||||||
import { encrypt, decrypt, encryptObject, decryptObject, hash } from './encryption.js';
|
|
||||||
|
|
||||||
// Type alias for compatibility
|
// Type alias for compatibility
|
||||||
type Database = sqlite3.Database;
|
export 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';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DatabaseService - Handles all local database operations
|
* DatabaseService - Manages SQLite database connection ONLY
|
||||||
* Provides CRUD operations with automatic encryption/decryption
|
* No business logic, no CRUD operations
|
||||||
* Maps between DB snake_case and TypeScript camelCase interfaces
|
* Just connection management and encryption key storage
|
||||||
*/
|
*/
|
||||||
export class DatabaseService {
|
export class DatabaseService {
|
||||||
private db: Database | null = null;
|
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 {
|
getDb(): Database | null {
|
||||||
if (!this.userEncryptionKey) throw new Error('Encryption key not set');
|
return this.db;
|
||||||
const encrypted = encrypt(data, this.userEncryptionKey);
|
|
||||||
return JSON.stringify(encrypted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt sensitive field
|
* Get user encryption key
|
||||||
*/
|
*/
|
||||||
private decryptField(encryptedData: string): string {
|
getEncryptionKey(): string | null {
|
||||||
if (!this.userEncryptionKey) throw new Error('Encryption key not set');
|
return this.userEncryptionKey;
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a single book by ID with all related data
|
* Get current user ID
|
||||||
*/
|
*/
|
||||||
getBook(bookId: string): BookProps | null {
|
getUserId(): string | null {
|
||||||
if (!this.db) throw new Error('Database not initialized');
|
return this.userId;
|
||||||
|
|
||||||
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
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encryption utilities using AES-256-GCM for local database encryption
|
* Encryption utilities using AES-256-CBC for local database encryption
|
||||||
* Each user has a unique encryption key derived from their userId and a master secret
|
* EXACTEMENT comme dans Fastify System.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const ALGORITHM = 'aes-256-gcm';
|
const ALGORITHM = 'aes-256-cbc';
|
||||||
const KEY_LENGTH = 32; // 256 bits
|
const KEY_LENGTH = 32; // 256 bits
|
||||||
const IV_LENGTH = 16; // 128 bits
|
const IV_LENGTH = 16; // 128 bits
|
||||||
const SALT_LENGTH = 64;
|
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
|
* Generate a unique encryption key for a user
|
||||||
@@ -54,77 +47,34 @@ function extractKeyFromStored(storedKey: string): Buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt sensitive data using AES-256-GCM
|
* Encrypt data with user key - EXACTEMENT comme Fastify
|
||||||
* @param data - Plain text data to encrypt
|
* @param data - 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
|
|
||||||
* @param userKey - User's encryption key
|
* @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 {
|
export function encryptDataWithUserKey(data: string, userKey: string): string {
|
||||||
const jsonString = JSON.stringify(obj);
|
const key = extractKeyFromStored(userKey);
|
||||||
return encrypt(jsonString, 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
|
* Decrypt data with user key - EXACTEMENT comme Fastify
|
||||||
* @param encryptedData - Encrypted data object
|
* @param encryptedData - Encrypted string with format "iv:encryptedData"
|
||||||
* @param userKey - User's encryption key
|
* @param userKey - User's encryption key
|
||||||
* @returns Decrypted and parsed object
|
* @returns Decrypted data
|
||||||
*/
|
*/
|
||||||
export function decryptObject<T>(encryptedData: EncryptedData, userKey: string): T {
|
export function decryptDataWithUserKey(encryptedData: string, userKey: string): string {
|
||||||
const decrypted = decrypt(encryptedData, userKey);
|
const [ivHex, encryptedHex] = encryptedData.split(':');
|
||||||
return JSON.parse(decrypted) as T;
|
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
|
* @param data - Data to hash
|
||||||
* @returns Hex encoded hash
|
* @returns Hex encoded hash
|
||||||
*/
|
*/
|
||||||
export function hash(data: string): string {
|
export function hashElement(data: string): string {
|
||||||
return crypto.createHash('sha256').update(data).digest('hex');
|
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