- Added `spells_enabled` column to `book_tools` table. - Introduced `book_spells` and `book_spell_tags` tables with related indexes for spell and tag storage. - Updated database initialization and migration scripts to include new tables and fields.
708 lines
24 KiB
TypeScript
708 lines
24 KiB
TypeScript
import sqlite3 from 'node-sqlite3-wasm';
|
|
|
|
type Database = sqlite3.Database;
|
|
|
|
/**
|
|
* SQLite schema based on the MySQL erit_main_db schema
|
|
* All tables use snake_case naming to match the server database
|
|
* Data is encrypted before storage and decrypted on retrieval
|
|
*/
|
|
|
|
// =============================================================================
|
|
// MIGRATIONS
|
|
// =============================================================================
|
|
|
|
const schemaVersion = 1;
|
|
|
|
/**
|
|
* DEV ONLY - S'exécute à chaque refresh, pas besoin de version
|
|
* Mets ta query, test, efface après
|
|
*/
|
|
const devQueries: string[] = [
|
|
|
|
];
|
|
|
|
const isDev = process.env.NODE_ENV === 'development';
|
|
|
|
function columnExists(db: Database, table: string, column: string): boolean {
|
|
const result = db.all(`PRAGMA table_info(${table})`) as { name: string }[];
|
|
return result?.some((row) => row.name === column) ?? false;
|
|
}
|
|
|
|
function addColumn(db: Database, table: string, column: string, type: string): void {
|
|
if (!columnExists(db, table, column)) {
|
|
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
|
|
}
|
|
}
|
|
|
|
function getDbVersion(db: Database): number {
|
|
const result = db.get('PRAGMA user_version') as { user_version: number } | undefined;
|
|
return result?.user_version ?? 0;
|
|
}
|
|
|
|
function setDbVersion(db: Database, version: number): void {
|
|
db.exec(`PRAGMA user_version = ${version}`);
|
|
}
|
|
|
|
/**
|
|
* Check if old _schema_version table exists
|
|
*/
|
|
function hasOldSchemaTable(db: Database): boolean {
|
|
const result = db.get(`
|
|
SELECT name FROM sqlite_master
|
|
WHERE type='table' AND name='_schema_version'
|
|
`) as { name: string } | undefined;
|
|
return !!result;
|
|
}
|
|
|
|
/**
|
|
* Get version from old _schema_version table
|
|
*/
|
|
function getOldSchemaVersion(db: Database): number {
|
|
try {
|
|
const result = db.get('SELECT version FROM _schema_version LIMIT 1') as { version: number } | undefined;
|
|
return result?.version ?? 0;
|
|
} catch {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrate from old _schema_version table to PRAGMA user_version
|
|
* Old system: v3 = all migrations done (book_tools created, NOT NULL fixes applied)
|
|
* New system: v1 = equivalent starting point
|
|
*/
|
|
function migrateFromOldSystem(db: Database): void {
|
|
const oldVersion = getOldSchemaVersion(db);
|
|
|
|
// Old v3 means all previous migrations were done
|
|
// Map to new system: old v3 = new v1
|
|
if (oldVersion >= 3) {
|
|
setDbVersion(db, schemaVersion);
|
|
}
|
|
|
|
// Add last_update column to book_tools if missing (was added after v3)
|
|
addColumn(db, 'book_tools', 'last_update', 'INTEGER DEFAULT 0');
|
|
|
|
// Add spells_enabled column to book_tools if missing
|
|
addColumn(db, 'book_tools', 'spells_enabled', 'INTEGER NOT NULL DEFAULT 0');
|
|
|
|
// Create book_spell_tags table if missing
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_spell_tags (
|
|
tag_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
name_hash TEXT NOT NULL,
|
|
color TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spell_tags_book ON book_spell_tags(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spell_tags_user ON book_spell_tags(user_id)`);
|
|
|
|
// Create book_spells table if missing
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_spells (
|
|
spell_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
name_hash TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
appearance TEXT NOT NULL,
|
|
tags TEXT NOT NULL,
|
|
power_level TEXT,
|
|
components TEXT,
|
|
limitations TEXT,
|
|
notes TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_book ON book_spells(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_user ON book_spells(user_id)`);
|
|
|
|
// Drop old schema version table
|
|
db.exec('DROP TABLE IF EXISTS _schema_version');
|
|
}
|
|
|
|
export function runMigrations(db: Database): void {
|
|
// DEV: run test queries (skip errors silently)
|
|
if (isDev && devQueries.length > 0) {
|
|
for (const query of devQueries) {
|
|
try {
|
|
db.exec(query);
|
|
} catch (_) { /* ignore */ }
|
|
}
|
|
}
|
|
|
|
// PROD only: versioned migrations
|
|
if (isDev) return;
|
|
|
|
// Migrate from old _schema_version system to PRAGMA user_version
|
|
if (hasOldSchemaTable(db)) {
|
|
migrateFromOldSystem(db);
|
|
return; // Migration done, no need to continue
|
|
}
|
|
|
|
const currentVersion = getDbVersion(db);
|
|
if (currentVersion >= schemaVersion) return;
|
|
|
|
// v1 - book_tools table + spell book tables (for fresh DBs or DBs without old system)
|
|
if (currentVersion < 1) {
|
|
// Book Tools
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_tools (
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
characters_enabled INTEGER NOT NULL DEFAULT 0,
|
|
worlds_enabled INTEGER NOT NULL DEFAULT 0,
|
|
locations_enabled INTEGER NOT NULL DEFAULT 0,
|
|
spells_enabled INTEGER NOT NULL DEFAULT 0,
|
|
last_update INTEGER DEFAULT 0,
|
|
UNIQUE (book_id, user_id),
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_book_tools_book ON book_tools(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_book_tools_user ON book_tools(user_id)`);
|
|
|
|
// Book Spell Tags
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_spell_tags (
|
|
tag_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
name_hash TEXT NOT NULL,
|
|
color TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spell_tags_book ON book_spell_tags(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spell_tags_user ON book_spell_tags(user_id)`);
|
|
|
|
// Book Spells
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_spells (
|
|
spell_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
name_hash TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
appearance TEXT NOT NULL,
|
|
tags TEXT NOT NULL,
|
|
power_level TEXT,
|
|
components TEXT,
|
|
limitations TEXT,
|
|
notes TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_book ON book_spells(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_user ON book_spells(user_id)`);
|
|
}
|
|
|
|
setDbVersion(db, schemaVersion);
|
|
}
|
|
|
|
/**
|
|
* Initialize the local SQLite database with all required tables
|
|
* @param db - SQLite database instance
|
|
*/
|
|
export function initializeSchema(db: Database): void {
|
|
// Enable foreign keys
|
|
db.exec('PRAGMA foreign_keys = ON');
|
|
|
|
// AI Conversations
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS ai_conversations (
|
|
conversation_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
mode TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
start_date INTEGER NOT NULL,
|
|
status INTEGER NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
summary TEXT,
|
|
convo_meta TEXT NOT NULL,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// AI Messages History
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS ai_messages_history (
|
|
message_id TEXT PRIMARY KEY,
|
|
conversation_id TEXT NOT NULL,
|
|
role TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
message_date INTEGER NOT NULL,
|
|
FOREIGN KEY (conversation_id) REFERENCES ai_conversations(conversation_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Acts
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_acts (
|
|
act_id INTEGER PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
last_update INTEGER DEFAULT 0
|
|
);
|
|
`);
|
|
|
|
// Book Act Summaries
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_act_summaries (
|
|
act_sum_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
act_index INTEGER NOT NULL,
|
|
summary TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book AI Guide Line
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_ai_guide_line (
|
|
user_id TEXT NOT NULL,
|
|
book_id TEXT NOT NULL,
|
|
global_resume TEXT,
|
|
themes TEXT,
|
|
verbe_tense INTEGER,
|
|
narrative_type INTEGER,
|
|
langue INTEGER,
|
|
dialogue_type INTEGER,
|
|
tone TEXT,
|
|
atmosphere TEXT,
|
|
current_resume TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
PRIMARY KEY (user_id, book_id),
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Chapters
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_chapters (
|
|
chapter_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
author_id TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
hashed_title TEXT,
|
|
words_count INTEGER,
|
|
chapter_order INTEGER,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Chapter Content
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_chapter_content (
|
|
content_id TEXT PRIMARY KEY,
|
|
chapter_id TEXT NOT NULL,
|
|
author_id TEXT NOT NULL,
|
|
version INTEGER NOT NULL DEFAULT 2,
|
|
content TEXT,
|
|
words_count INTEGER NOT NULL,
|
|
time_on_it INTEGER NOT NULL DEFAULT 0,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (chapter_id) REFERENCES book_chapters(chapter_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Chapter Infos
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_chapter_infos (
|
|
chapter_info_id TEXT PRIMARY KEY,
|
|
chapter_id TEXT,
|
|
act_id INTEGER,
|
|
incident_id TEXT,
|
|
plot_point_id TEXT,
|
|
book_id TEXT,
|
|
author_id TEXT,
|
|
summary TEXT,
|
|
goal TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (chapter_id) REFERENCES book_chapters(chapter_id) ON DELETE CASCADE,
|
|
FOREIGN KEY (incident_id) REFERENCES book_incidents(incident_id) ON DELETE CASCADE,
|
|
FOREIGN KEY (plot_point_id) REFERENCES book_plot_points(plot_point_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Characters
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_characters (
|
|
character_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
first_name TEXT NOT NULL,
|
|
last_name TEXT,
|
|
category TEXT NOT NULL,
|
|
title TEXT,
|
|
image TEXT,
|
|
role TEXT,
|
|
biography TEXT,
|
|
history TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Character Attributes
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_characters_attributes (
|
|
attr_id TEXT PRIMARY KEY,
|
|
character_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
attribute_name TEXT NOT NULL,
|
|
attribute_value TEXT NOT NULL,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (character_id) REFERENCES book_characters(character_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Character Relations
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_characters_relations (
|
|
rel_id INTEGER PRIMARY KEY,
|
|
character_id INTEGER NOT NULL,
|
|
char_name TEXT NOT NULL,
|
|
type TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
history TEXT NOT NULL,
|
|
last_update INTEGER DEFAULT 0
|
|
);
|
|
`);
|
|
|
|
// Book Guide Line
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_guide_line (
|
|
user_id TEXT NOT NULL,
|
|
book_id TEXT NOT NULL,
|
|
tone TEXT,
|
|
atmosphere TEXT,
|
|
writing_style TEXT,
|
|
themes TEXT,
|
|
symbolism TEXT,
|
|
motifs TEXT,
|
|
narrative_voice TEXT,
|
|
pacing TEXT,
|
|
intended_audience TEXT,
|
|
key_messages TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
PRIMARY KEY (user_id, book_id),
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Incidents
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_incidents (
|
|
incident_id TEXT PRIMARY KEY,
|
|
author_id TEXT NOT NULL,
|
|
book_id TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
hashed_title TEXT NOT NULL,
|
|
summary TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Issues
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_issues (
|
|
issue_id TEXT PRIMARY KEY,
|
|
author_id TEXT NOT NULL,
|
|
book_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
hashed_issue_name TEXT NOT NULL,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Location
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_location (
|
|
loc_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
loc_name TEXT NOT NULL,
|
|
loc_original_name TEXT NOT NULL,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Plot Points
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_plot_points (
|
|
plot_point_id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
hashed_title TEXT NOT NULL,
|
|
summary TEXT,
|
|
linked_incident_id TEXT,
|
|
author_id TEXT NOT NULL,
|
|
book_id TEXT NOT NULL,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book World
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_world (
|
|
world_id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
hashed_name TEXT NOT NULL,
|
|
author_id TEXT NOT NULL,
|
|
book_id TEXT NOT NULL,
|
|
history TEXT,
|
|
politics TEXT,
|
|
economy TEXT,
|
|
religion TEXT,
|
|
languages TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book World Elements
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_world_elements (
|
|
element_id TEXT PRIMARY KEY,
|
|
world_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
element_type INTEGER NOT NULL,
|
|
name TEXT NOT NULL,
|
|
original_name TEXT NOT NULL,
|
|
description TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (world_id) REFERENCES book_world(world_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Erit Books
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS erit_books (
|
|
book_id TEXT PRIMARY KEY,
|
|
type TEXT NOT NULL,
|
|
author_id TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
hashed_title TEXT NOT NULL,
|
|
sub_title TEXT,
|
|
hashed_sub_title TEXT,
|
|
summary TEXT,
|
|
serie_id INTEGER,
|
|
desired_release_date TEXT,
|
|
desired_word_count INTEGER,
|
|
words_count INTEGER,
|
|
cover_image TEXT,
|
|
last_update INTEGER DEFAULT 0
|
|
);
|
|
`);
|
|
|
|
// Erit Book Series
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS erit_book_series (
|
|
serie_id INTEGER PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
author_id INTEGER NOT NULL
|
|
);
|
|
`);
|
|
|
|
// Erit Editor Settings
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS erit_editor (
|
|
user_id TEXT,
|
|
type TEXT NOT NULL,
|
|
text_size INTEGER NOT NULL,
|
|
text_intent INTEGER NOT NULL,
|
|
interline TEXT NOT NULL,
|
|
paper_width INTEGER NOT NULL,
|
|
theme TEXT NOT NULL,
|
|
focus INTEGER NOT NULL
|
|
);
|
|
`);
|
|
|
|
// Erit Users
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS erit_users (
|
|
user_id TEXT PRIMARY KEY,
|
|
first_name TEXT NOT NULL,
|
|
last_name TEXT NOT NULL,
|
|
username TEXT NOT NULL,
|
|
email TEXT NOT NULL,
|
|
origin_email TEXT NOT NULL,
|
|
origin_username TEXT NOT NULL,
|
|
author_name TEXT,
|
|
origin_author_name TEXT,
|
|
plateform TEXT NOT NULL,
|
|
social_id TEXT,
|
|
user_group INTEGER NOT NULL DEFAULT 4,
|
|
password TEXT,
|
|
term_accepted INTEGER NOT NULL DEFAULT 0,
|
|
verify_code TEXT,
|
|
reg_date INTEGER NOT NULL,
|
|
account_verified INTEGER NOT NULL DEFAULT 0,
|
|
erite_points INTEGER NOT NULL DEFAULT 100,
|
|
stripe_customer_id TEXT,
|
|
credits_balance REAL DEFAULT 0
|
|
);
|
|
`);
|
|
|
|
// Location Element
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS location_element (
|
|
element_id TEXT PRIMARY KEY,
|
|
location TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
element_name TEXT NOT NULL,
|
|
original_name TEXT NOT NULL,
|
|
element_description TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (location) REFERENCES book_location(loc_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Location Sub Element
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS location_sub_element (
|
|
sub_element_id TEXT PRIMARY KEY,
|
|
element_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
sub_elem_name TEXT NOT NULL,
|
|
original_name TEXT NOT NULL,
|
|
sub_elem_description TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (element_id) REFERENCES location_element(element_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// User Keys
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS user_keys (
|
|
user_id TEXT NOT NULL,
|
|
brand TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
actif INTEGER NOT NULL DEFAULT 1,
|
|
FOREIGN KEY (user_id) REFERENCES erit_users(user_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// User Last Chapter
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS user_last_chapter (
|
|
user_id TEXT NOT NULL,
|
|
book_id TEXT NOT NULL,
|
|
chapter_id TEXT NOT NULL,
|
|
version INTEGER NOT NULL,
|
|
PRIMARY KEY (user_id, book_id),
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE,
|
|
FOREIGN KEY (chapter_id) REFERENCES book_chapters(chapter_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Tools
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_tools (
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
characters_enabled INTEGER NOT NULL DEFAULT 0,
|
|
worlds_enabled INTEGER NOT NULL DEFAULT 0,
|
|
locations_enabled INTEGER NOT NULL DEFAULT 0,
|
|
spells_enabled INTEGER NOT NULL DEFAULT 0,
|
|
last_update INTEGER DEFAULT 0,
|
|
UNIQUE (book_id, user_id),
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Spell Tags
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_spell_tags (
|
|
tag_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
name_hash TEXT NOT NULL,
|
|
color TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Book Spells
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS book_spells (
|
|
spell_id TEXT PRIMARY KEY,
|
|
book_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
name_hash TEXT NOT NULL,
|
|
description TEXT NOT NULL,
|
|
appearance TEXT NOT NULL,
|
|
tags TEXT NOT NULL,
|
|
power_level TEXT,
|
|
components TEXT,
|
|
limitations TEXT,
|
|
notes TEXT,
|
|
last_update INTEGER DEFAULT 0,
|
|
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
|
);
|
|
`);
|
|
|
|
// Create indexes for better performance
|
|
createIndexes(db);
|
|
}
|
|
|
|
/**
|
|
* Create indexes for frequently queried columns
|
|
*/
|
|
function createIndexes(db: Database): void {
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_ai_conversations_book ON ai_conversations(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_ai_conversations_user ON ai_conversations(user_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_ai_messages_conversation ON ai_messages_history(conversation_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_chapters_book ON book_chapters(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_chapter_content_chapter ON book_chapter_content(chapter_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_characters_book ON book_characters(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_character_attrs_character ON book_characters_attributes(character_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_world_book ON book_world(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_world_elements_world ON book_world_elements(world_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_book_tools_book ON book_tools(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_book_tools_user ON book_tools(user_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spell_tags_book ON book_spell_tags(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spell_tags_user ON book_spell_tags(user_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_book ON book_spells(book_id)`);
|
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_spells_user ON book_spells(user_id)`);
|
|
}
|
|
|
|
/**
|
|
* Drop all tables (for testing/reset)
|
|
*/
|
|
export function dropAllTables(db: Database): void {
|
|
const tables = db.all(`
|
|
SELECT name FROM sqlite_master
|
|
WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
|
`) as { name: string }[];
|
|
|
|
db.exec('PRAGMA foreign_keys = OFF');
|
|
|
|
for (const row of tables) {
|
|
db.exec(`DROP TABLE IF EXISTS ${row.name}`);
|
|
}
|
|
|
|
db.exec('PRAGMA foreign_keys = ON');
|
|
}
|