Upgrade database schema to version 2 and remove unused meta_* columns

- Increment `SCHEMA_VERSION` to 2 in `schema.ts`.
- Remove all `meta_*` columns from database tables.
- Add migration logic to handle schema upgrades and clean up unused columns.
- Modify database models and repository methods to exclude `meta_*` fields for stricter typings and improved structure.
- Refactor and optimize query statements across repositories to align with new schema changes.
This commit is contained in:
natreex
2025-11-26 19:17:40 -05:00
parent 736b9a3609
commit 9648d9e9be
13 changed files with 178 additions and 131 deletions

View File

@@ -11,10 +11,12 @@ import AlertBox from "@/components/AlertBox";
import {useTranslations} from "next-intl";
import InlineAddInput from "@/components/form/InlineAddInput";
import {LangContext} from "@/context/LangContext";
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
export default function ScribeChapterComponent() {
const t = useTranslations();
const {lang} = useContext(LangContext)
const {lang} = useContext(LangContext);
const {isCurrentlyOffline} = useContext<OfflineContextType>(OfflineContext);
const {book} = useContext(BookContext);
const {chapter, setChapter} = useContext(ChapterContext);
@@ -43,18 +45,16 @@ export default function ScribeChapterComponent() {
useEffect((): void => {
if (chapter?.chapterId && scrollContainerRef.current) {
// Small delay to ensure DOM is ready
setTimeout(() => {
const element = chapterRefs.current.get(chapter.chapterId);
const container = scrollContainerRef.current;
if (element && container) {
const containerRect = container.getBoundingClientRect();
const elementRect = element.getBoundingClientRect();
const containerRect:DOMRect = container.getBoundingClientRect();
const elementRect:DOMRect = element.getBoundingClientRect();
// Calculate relative position
const relativeTop = elementRect.top - containerRect.top + container.scrollTop;
const scrollPosition = relativeTop - (containerRect.height / 2) + (elementRect.height / 2);
const relativeTop:number = elementRect.top - containerRect.top + container.scrollTop;
const scrollPosition:number = relativeTop - (containerRect.height / 2) + (elementRect.height / 2);
container.scrollTo({
top: Math.max(0, scrollPosition),
@@ -72,7 +72,12 @@ export default function ScribeChapterComponent() {
async function getChapterList(): Promise<void> {
try {
const response: ChapterListProps[] = await System.authGetQueryToServer<ChapterListProps[]>(`book/chapters?id=${book?.bookId}`, userToken, lang);
let response: ChapterListProps[]|null;
if (isCurrentlyOffline()){
response = await window.electron.invoke<ChapterListProps[]>('db:book:chapters', book?.bookId)
} else {
response = await System.authGetQueryToServer<ChapterListProps[]>(`book/chapters?id=${book?.bookId}`, userToken, lang);
}
if (response) {
setChapters(response);
}
@@ -88,11 +93,20 @@ export default function ScribeChapterComponent() {
async function getChapter(chapterId: string): Promise<void> {
const version: number = chapter?.chapterContent.version ? chapter?.chapterContent.version : 2;
try {
const response: ChapterProps = await System.authGetQueryToServer<ChapterProps>(`chapter/whole`, userToken, lang, {
let response: ChapterProps | null = null
if (isCurrentlyOffline()) {
response = await window.electron.invoke<ChapterProps>('db:chapter:whole', {
bookid: book?.bookId,
id: chapterId,
version: version,
})
} else {
response = await System.authGetQueryToServer<ChapterProps>(`chapter/whole`, userToken, lang, {
bookid: book?.bookId,
id: chapterId,
version: version,
});
}
if (!response) {
errorMessage(t("scribeChapterComponent.errorFetchChapter"));
return;
@@ -173,11 +187,20 @@ export default function ScribeChapterComponent() {
}
const chapterTitle: string = chapterOrder >= 0 ? newChapterName : book?.title as string;
try {
const chapterId: string = await System.authPostToServer<string>('chapter/add', {
let chapterId:string|null = null;
if (isCurrentlyOffline()){
chapterId = await window.electron.invoke<string>('db:chapter:create', {
bookId: book?.bookId,
chapterOrder: chapterOrder,
title: chapterTitle
})
} else {
chapterId = await System.authPostToServer<string>('chapter/add', {
bookId: book?.bookId,
chapterOrder: chapterOrder,
title: chapterTitle
}, userToken, lang);
}
if (!chapterId) {
errorMessage(t("scribeChapterComponent.errorChapterSubmit", {chapterName: newChapterName}));
return;

View File

@@ -16,7 +16,6 @@ import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
export default function ComposerRightBar() {
const {book} = useContext(BookContext);
const {chapter} = useContext(ChapterContext);
const t = useTranslations();

View File

@@ -3,6 +3,7 @@
import type {Context, Dispatch, JSX, ReactNode, SetStateAction} from 'react';
import {createContext, useCallback, useState} from 'react';
import AlertStack from '@/components/AlertStack';
import {cleanErrorMessage} from '@/lib/errors';
export type AlertType = 'success' | 'error' | 'info' | 'warning';
@@ -53,7 +54,7 @@ export function AlertProvider({children}: AlertProviderProps): JSX.Element {
}, [addAlert]);
const errorMessage: (message: string) => void = useCallback((message: string): void => {
addAlert('error', message);
addAlert('error', cleanErrorMessage(message));
}, [addAlert]);
const infoMessage: (message: string) => void = useCallback((message: string): void => {

View File

@@ -1,7 +1,7 @@
import sqlite3 from 'node-sqlite3-wasm';
import path from 'path';
import { app } from 'electron';
import { initializeSchema } from './schema.js';
import { initializeSchema, runMigrations } from './schema.js';
// Type alias for compatibility
export type Database = sqlite3.Database;
@@ -36,9 +36,12 @@ export class DatabaseService {
this.userEncryptionKey = encryptionKey;
this.userId = userId;
// Initialize schema
// Initialize schema (creates tables if they don't exist)
initializeSchema(this.db);
// Run migrations (updates existing tables if needed)
runMigrations(this.db);
console.log(`Database initialized for user ${userId} at ${dbPath}`);
}

View File

@@ -844,8 +844,6 @@ export default class Book {
const decryptedChapters: any[] = [];
for (const chapter of chapters) {
if (!chapter.meta_chapter) continue;
if (!chapter.meta_chapter_content) continue;
decryptedChapters.push({
id: '',
title: chapter.title ? System.decryptDataWithUserKey(chapter.title, userKey) : '',

View File

@@ -206,11 +206,7 @@ export default class Character {
const personnage: CompleteCharacterProps | undefined = completeCharactersMap.get(character.character_id);
if (!character.attr_meta) {
continue;
}
if (!personnage) {
if (!character.attribute_name || !personnage) {
continue;
}
const decryptedName: string = System.decryptDataWithUserKey(character.attribute_name, userKey);

View File

@@ -15,7 +15,6 @@ export interface BookQuery extends Record<string, SQLiteValue> {
desired_word_count: number | null;
words_count: number | null;
cover_image: string | null;
book_meta: string | null;
}
export interface GuideLineQuery extends Record<string, SQLiteValue> {
@@ -29,7 +28,6 @@ export interface GuideLineQuery extends Record<string, SQLiteValue> {
pacing: string;
intended_audience: string;
key_messages: string;
meta_guide_line: string;
}
export interface PlotPointQuery extends Record<string, SQLiteValue> {
@@ -37,43 +35,32 @@ export interface PlotPointQuery extends Record<string, SQLiteValue> {
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> {
@@ -84,12 +71,10 @@ export interface WorldQuery extends Record<string, SQLiteValue> {
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 {
@@ -118,7 +103,7 @@ 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[];
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 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);
@@ -150,7 +135,7 @@ export default class BookRepo {
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;
result = db.get('SELECT book_id, author_id, title, summary, sub_title, cover_image, desired_release_date, desired_word_count, words_count 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}`);
@@ -201,7 +186,7 @@ export default class BookRepo {
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[];
return db.all('SELECT act_index, summary 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}`);
@@ -215,7 +200,7 @@ export default class BookRepo {
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[];
return db.all('SELECT incident_id, title, summary 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}`);
@@ -229,7 +214,7 @@ export default class BookRepo {
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[];
return db.all('SELECT plot_point_id, title, summary, linked_incident_id 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}`);
@@ -243,7 +228,7 @@ export default class BookRepo {
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[];
return db.all('SELECT issue_id, name 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}`);
@@ -278,7 +263,7 @@ export default class BookRepo {
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;
return db.get('SELECT cover_image 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}`);
@@ -529,7 +514,7 @@ export default class BookRepo {
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[];
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, element.element_id AS element_id, element.name AS element_name, element.description AS element_description, element.element_type 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}`);
@@ -709,7 +694,7 @@ export default class BookRepo {
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[];
const result = db.all('SELECT title, chapter_order, content.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.`);
}

View File

@@ -7,20 +7,17 @@ export interface ChapterContentQueryResult extends Record<string, SQLiteValue>{
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,
content: string;
}
export interface ChapterQueryResult extends Record<string, SQLiteValue>{
chapter_id: string;
title: string;
chapter_order:number;
meta_chapter: string;
chapter_order: number;
}
export interface ActChapterQuery extends Record<string, SQLiteValue>{
@@ -28,38 +25,31 @@ export interface ActChapterQuery extends Record<string, SQLiteValue>{
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;
summary: 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>{
@@ -88,7 +78,7 @@ export default class ChapterRepo{
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) VALUES (?,?,?,?,?,?,?)', [chapterId, userId, bookId, title, hashedTitle, wordsCount, chapterOrder]);
result = db.run('INSERT INTO book_chapters (chapter_id, author_id, book_id, title, hashed_title, words_count, chapter_order) VALUES (?,?,?,?,?,?,?)', [chapterId, userId, bookId, title, hashedTitle, wordsCount, chapterOrder]);
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
@@ -109,7 +99,7 @@ export default class ChapterRepo{
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 = ?';
const query: string = 'SELECT chapter.chapter_id as chapter_id, chapter.title as title, chapter.chapter_order, chapter.words_count, content.content AS content, content.version as version 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) {
@@ -129,7 +119,7 @@ export default class ChapterRepo{
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`;
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.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) {
@@ -144,7 +134,7 @@ export default class ChapterRepo{
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[];
return db.all('SELECT ci.chapter_info_id AS chapter_info_id, ci.chapter_id AS chapter_id, chapter.title, chapter.chapter_order, ci.act_id, ci.incident_id AS incident_id, ci.plot_point_id AS plot_point_id, ci.summary, ci.goal 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}`);
@@ -159,7 +149,7 @@ export default class ChapterRepo{
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[];
return db.all('SELECT chapter_id, title, chapter_order 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}`);
@@ -207,7 +197,7 @@ export default class ChapterRepo{
}
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, '', '', '']);
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) VALUES (?,?,?,?,?,?,?,?,?)', [chapterInfoId, chapterId, actId, bookId, userId, incidentId, plotId, '', '']);
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
@@ -302,7 +292,7 @@ export default class ChapterRepo{
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[];
return db.all('SELECT version, content, words_count 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}`);
@@ -354,7 +344,7 @@ export default class ChapterRepo{
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[];
return db.all('SELECT chapter_info_id, chapter.act_id, act_sum.summary, chapter.summary AS chapter_summary, chapter.goal AS chapter_goal, chapter.incident_id, incident.title AS incident_title, incident.summary AS incident_summary, chapter.plot_point_id, plot.title AS plot_title, plot.summary AS plot_summary 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}`);
@@ -370,7 +360,7 @@ export default class ChapterRepo{
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;
result = db.get('SELECT content.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}`);
@@ -390,7 +380,7 @@ export default class ChapterRepo{
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;
result = db.get('SELECT 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}`);

View File

@@ -11,14 +11,12 @@ export interface CharacterResult extends Record<string, SQLiteValue> {
role: string;
biography: string;
history: string;
char_meta: string;
}
export interface AttributeResult extends Record<string, SQLiteValue> {
attr_id: string;
attribute_name: string;
attribute_value: string;
attr_meta: string;
}
export interface CompleteCharacterResult extends Record<string, SQLiteValue> {
@@ -30,10 +28,8 @@ export interface CompleteCharacterResult extends Record<string, SQLiteValue> {
role: string;
biography: string;
history: string;
char_meta: string;
attribute_name: string;
attribute_value: string;
attr_meta: string;
}
export default class CharacterRepo {
@@ -41,7 +37,7 @@ export default class CharacterRepo {
let result: CharacterResult[];
try {
const db: Database = System.getDb();
result = db.all('SELECT character_id,`first_name`,`last_name`,`title`,`category`,`image`,`role`,`biography`,`history`,`char_meta` FROM `book_characters` WHERE `book_id`=? AND user_id=?', [bookId, userId]) as CharacterResult[];
result = db.all('SELECT character_id, first_name, last_name, title, category, image, role, biography, history FROM book_characters WHERE book_id=? AND user_id=?', [bookId, userId]) as CharacterResult[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
@@ -132,7 +128,7 @@ export default class CharacterRepo {
let result: AttributeResult[];
try {
const db: Database = System.getDb();
result = db.all('SELECT attr_id, attribute_name, attribute_value, attr_meta FROM `book_characters_attributes` WHERE `character_id`=? AND `user_id`=?', [characterId, userId]) as AttributeResult[];
result = db.all('SELECT attr_id, attribute_name, attribute_value FROM book_characters_attributes WHERE character_id=? AND user_id=?', [characterId, userId]) as AttributeResult[];
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
@@ -149,7 +145,7 @@ export default class CharacterRepo {
let result: CompleteCharacterResult[];
try {
const db: Database = System.getDb();
let query: string = 'SELECT charac.`character_id`,`first_name`,`last_name`,`category`,`title`,`role`,`biography`,`history`,`char_meta`,`attribute_name`,`attribute_value`,attr_meta FROM book_characters AS charac LEFT JOIN book_characters_attributes AS attr ON charac.character_id=attr.character_id WHERE charac.user_id=? AND charac.book_id=?';
let query: string = 'SELECT charac.character_id, first_name, last_name, category, title, role, biography, history, attribute_name, attribute_value FROM book_characters AS charac LEFT JOIN book_characters_attributes AS attr ON charac.character_id=attr.character_id WHERE charac.user_id=? AND charac.book_id=?';
let values: any[] = [userId, bookId];
if (tags && tags.length > 0) {
const placeholders: string = tags.map((): string => '?').join(',');

View File

@@ -4,35 +4,28 @@ import System from "../System.js";
export interface LocationQueryResult extends Record<string, SQLiteValue> {
loc_id: string;
loc_name: string;
loc_meta: string;
element_id: string;
element_name: string;
element_description: string;
element_meta: string;
sub_element_id: string;
sub_elem_name: string;
sub_elem_description: string;
sub_elem_meta: string;
}
export interface LocationElementQueryResult extends Record<string, SQLiteValue> {
sub_element_id: string;
sub_elem_name: string;
sub_elem_description: string;
sub_elem_meta: string;
element_id: string;
element_name: string;
element_description: string;
element_meta: string;
}
export interface LocationByTagResult extends Record<string, SQLiteValue> {
element_name: string;
element_description: string;
element_meta: string;
sub_elem_name: string;
sub_elem_description: string;
sub_elem_meta: string;
}
export default class LocationRepo {
@@ -40,7 +33,7 @@ export default class LocationRepo {
let result: LocationQueryResult[];
try {
const db: Database = System.getDb();
const query = 'SELECT loc_id,loc_name, loc_meta, element.element_id AS element_id, element.element_name, element.element_description, element.element_meta, sub_elem.sub_element_id AS sub_element_id,sub_elem.sub_elem_name,sub_elem.sub_elem_description,sub_elem.sub_elem_meta FROM `book_location` AS location LEFT JOIN location_element AS element ON location.loc_id=element.location LEFT JOIN location_sub_element AS sub_elem ON element.element_id=sub_elem.element_id WHERE location.user_id=? AND location.book_id=?';
const query = 'SELECT loc_id, loc_name, element.element_id AS element_id, element.element_name, element.element_description, sub_elem.sub_element_id AS sub_element_id, sub_elem.sub_elem_name, sub_elem.sub_elem_description FROM book_location AS location LEFT JOIN location_element AS element ON location.loc_id=element.location LEFT JOIN location_sub_element AS sub_elem ON element.element_id=sub_elem.element_id WHERE location.user_id=? AND location.book_id=?';
result = db.all(query, [userId, bookId]) as LocationQueryResult[];
} catch (e: unknown) {
if (e instanceof Error) {
@@ -217,7 +210,7 @@ export default class LocationRepo {
let result: LocationElementQueryResult[];
try {
const db: Database = System.getDb();
const query = 'SELECT se.sub_element_id AS sub_element_id, se.sub_elem_name, se.sub_elem_description, se.sub_elem_meta, el.element_id AS element_id, el.element_name, el.element_description, el.element_meta FROM location_sub_element AS se RIGHT JOIN location_element AS el ON se.element_id = el.element_id LEFT JOIN book_location AS lo ON el.location = lo.loc_id WHERE lo.book_id = ? AND lo.user_id = ?';
const query = 'SELECT se.sub_element_id AS sub_element_id, se.sub_elem_name, se.sub_elem_description, el.element_id AS element_id, el.element_name, el.element_description FROM location_sub_element AS se RIGHT JOIN location_element AS el ON se.element_id = el.element_id LEFT JOIN book_location AS lo ON el.location = lo.loc_id WHERE lo.book_id = ? AND lo.user_id = ?';
result = db.all(query, [bookId, userId]) as LocationElementQueryResult[];
} catch (e: unknown) {
if (e instanceof Error) {
@@ -242,10 +235,8 @@ export default class LocationRepo {
const query: string = `
SELECT el.element_name,
el.element_description,
el.element_meta,
se.sub_elem_name,
se.sub_elem_description,
se.sub_elem_meta
se.sub_elem_description
FROM location_element AS el
LEFT JOIN location_sub_element AS se ON el.element_id = se.element_id
WHERE el.user_id = ?

View File

@@ -9,12 +9,11 @@ export interface UserInfosQueryResponse extends Record<string, SQLiteValue> {
plateform: string;
term_accepted: number;
account_verified: number;
user_meta: string;
author_name: string;
writing_lang: number,
writing_level: number,
rite_points: number,
user_group: number,
writing_lang: number;
writing_level: number;
rite_points: number;
user_group: number;
}
export interface CredentialResponse {
@@ -35,10 +34,9 @@ interface UserResponse {
export interface UserAccountQuery extends Record<string, SQLiteValue> {
first_name: string;
last_name: string;
username: string
username: string;
author_name: string;
email: string;
user_meta: string;
}
export interface GuideTourResult extends Record<string, SQLiteValue> {
@@ -53,8 +51,8 @@ export default class UserRepo {
const db: Database = System.getDb();
const query = `INSERT INTO erit_users (user_id, first_name, last_name, username, email, origin_email,
origin_username, plateform, term_accepted,
account_verified, user_meta, reg_date)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
account_verified, reg_date)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const values: (string | null | number)[] = [
uuId,
firstName,
@@ -66,7 +64,6 @@ export default class UserRepo {
'desktop', // plateform
0, // term_accepted
1, // account_verified
'{}', // user_meta (JSON empty object)
Date.now() // reg_date (current timestamp)
];
result = db.run(query, values);
@@ -90,7 +87,7 @@ export default class UserRepo {
let result;
try {
const db: Database = System.getDb();
result = db.get('SELECT `first_name`, `last_name`, `username`, `email`, `plateform`, `term_accepted`, `account_verified`, user_meta, author_name, erite_points AS rite_points, user_group FROM `erit_users` WHERE user_id=?', [userId]);
result = db.get('SELECT first_name, last_name, username, email, plateform, term_accepted, account_verified, author_name, erite_points AS rite_points, user_group FROM erit_users WHERE user_id=?', [userId]);
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);
@@ -126,7 +123,7 @@ export default class UserRepo {
let result;
try {
const db: Database = System.getDb();
result = db.get('SELECT `first_name`, `last_name`, `username`,`author_name`, `email`,`user_meta` FROM `erit_users` WHERE user_id=?', [userId]);
result = db.get('SELECT first_name, last_name, username, author_name, email FROM erit_users WHERE user_id=?', [userId]);
} catch (e: unknown) {
if (e instanceof Error) {
console.error(`DB Error: ${e.message}`);

View File

@@ -8,7 +8,7 @@ type Database = sqlite3.Database;
* Data is encrypted before storage and decrypted on retrieval
*/
export const SCHEMA_VERSION = 1;
export const SCHEMA_VERSION = 2;
/**
* Initialize the local SQLite database with all required tables
@@ -66,7 +66,6 @@ export function initializeSchema(db: Database): void {
role TEXT NOT NULL,
message TEXT NOT NULL,
message_date INTEGER NOT NULL,
meta_message TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (conversation_id) REFERENCES ai_conversations(conversation_id) ON DELETE CASCADE
);
@@ -88,7 +87,6 @@ export function initializeSchema(db: Database): void {
user_id TEXT NOT NULL,
act_index INTEGER NOT NULL,
summary TEXT,
meta_acts TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
);
@@ -108,7 +106,6 @@ export function initializeSchema(db: Database): void {
tone TEXT,
atmosphere TEXT,
current_resume TEXT,
meta TEXT NOT NULL,
synced INTEGER DEFAULT 0,
PRIMARY KEY (user_id, book_id),
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
@@ -125,7 +122,6 @@ export function initializeSchema(db: Database): void {
hashed_title TEXT,
words_count INTEGER,
chapter_order INTEGER,
meta_chapter TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
);
@@ -140,7 +136,6 @@ export function initializeSchema(db: Database): void {
version INTEGER NOT NULL DEFAULT 2,
content TEXT NOT NULL,
words_count INTEGER NOT NULL,
meta_chapter_content TEXT NOT NULL,
time_on_it INTEGER NOT NULL DEFAULT 0,
synced INTEGER DEFAULT 0,
FOREIGN KEY (chapter_id) REFERENCES book_chapters(chapter_id) ON DELETE CASCADE
@@ -159,7 +154,6 @@ export function initializeSchema(db: Database): void {
author_id TEXT,
summary TEXT NOT NULL,
goal TEXT NOT NULL,
meta_chapter_info TEXT NOT NULL,
synced 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,
@@ -181,7 +175,6 @@ export function initializeSchema(db: Database): void {
role TEXT,
biography TEXT,
history TEXT,
char_meta TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
);
@@ -195,7 +188,6 @@ export function initializeSchema(db: Database): void {
user_id TEXT NOT NULL,
attribute_name TEXT NOT NULL,
attribute_value TEXT NOT NULL,
attr_meta TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (character_id) REFERENCES book_characters(character_id) ON DELETE CASCADE
);
@@ -229,7 +221,6 @@ export function initializeSchema(db: Database): void {
pacing TEXT NOT NULL,
intended_audience TEXT NOT NULL,
key_messages TEXT NOT NULL,
meta_guide_line TEXT NOT NULL,
synced INTEGER DEFAULT 0,
PRIMARY KEY (user_id, book_id),
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
@@ -245,7 +236,6 @@ export function initializeSchema(db: Database): void {
title TEXT NOT NULL,
hashed_title TEXT NOT NULL,
summary TEXT,
meta_incident TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
);
@@ -259,7 +249,6 @@ export function initializeSchema(db: Database): void {
book_id TEXT NOT NULL,
name TEXT NOT NULL,
hashed_issue_name TEXT NOT NULL,
meta_issue TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
);
@@ -273,7 +262,6 @@ export function initializeSchema(db: Database): void {
user_id TEXT NOT NULL,
loc_name TEXT NOT NULL,
loc_original_name TEXT NOT NULL,
loc_meta TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
);
@@ -289,7 +277,6 @@ export function initializeSchema(db: Database): void {
linked_incident_id TEXT,
author_id TEXT NOT NULL,
book_id TEXT NOT NULL,
meta_plot TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
);
@@ -308,7 +295,6 @@ export function initializeSchema(db: Database): void {
economy TEXT,
religion TEXT,
languages TEXT,
meta_world TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
);
@@ -324,7 +310,6 @@ export function initializeSchema(db: Database): void {
name TEXT NOT NULL,
original_name TEXT NOT NULL,
description TEXT,
meta_element TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (world_id) REFERENCES book_world(world_id) ON DELETE CASCADE
);
@@ -346,7 +331,6 @@ export function initializeSchema(db: Database): void {
desired_word_count INTEGER,
words_count INTEGER,
cover_image TEXT,
book_meta TEXT,
synced INTEGER DEFAULT 0
);
`);
@@ -395,7 +379,6 @@ export function initializeSchema(db: Database): void {
verify_code TEXT,
reg_date INTEGER NOT NULL,
account_verified INTEGER NOT NULL DEFAULT 0,
user_meta TEXT NOT NULL,
erite_points INTEGER NOT NULL DEFAULT 100,
stripe_customer_id TEXT,
credits_balance REAL DEFAULT 0,
@@ -412,7 +395,6 @@ export function initializeSchema(db: Database): void {
element_name TEXT NOT NULL,
original_name TEXT NOT NULL,
element_description TEXT,
element_meta TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (location) REFERENCES book_location(loc_id) ON DELETE CASCADE
);
@@ -427,7 +409,6 @@ export function initializeSchema(db: Database): void {
sub_elem_name TEXT NOT NULL,
original_name TEXT NOT NULL,
sub_elem_description TEXT,
sub_elem_meta TEXT NOT NULL,
synced INTEGER DEFAULT 0,
FOREIGN KEY (element_id) REFERENCES location_element(element_id) ON DELETE CASCADE
);
@@ -523,3 +504,90 @@ export function dropAllTables(db: Database): void {
db.exec('PRAGMA foreign_keys = ON');
}
/**
* Get current schema version from database
*/
function getDbSchemaVersion(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;
}
}
/**
* Set schema version in database
*/
function setDbSchemaVersion(db: Database, version: number): void {
db.exec('CREATE TABLE IF NOT EXISTS _schema_version (version INTEGER PRIMARY KEY)');
db.run('DELETE FROM _schema_version');
db.run('INSERT INTO _schema_version (version) VALUES (?)', [version]);
}
/**
* Check if a column exists in a table
*/
function columnExists(db: Database, tableName: string, columnName: string): boolean {
const columns = db.all(`PRAGMA table_info(${tableName})`) as { name: string }[];
return columns.some(col => col.name === columnName);
}
/**
* Safely drop a column if it exists
*/
function dropColumnIfExists(db: Database, tableName: string, columnName: string): void {
if (columnExists(db, tableName, columnName)) {
try {
db.exec(`ALTER TABLE ${tableName} DROP COLUMN ${columnName}`);
console.log(`[Migration] Dropped column ${columnName} from ${tableName}`);
} catch (e) {
console.error(`[Migration] Failed to drop column ${columnName} from ${tableName}:`, e);
}
}
}
/**
* Run migrations to update schema from one version to another
*/
export function runMigrations(db: Database): void {
const currentVersion = getDbSchemaVersion(db);
if (currentVersion >= SCHEMA_VERSION) {
return;
}
console.log(`[Migration] Upgrading schema from version ${currentVersion} to ${SCHEMA_VERSION}`);
// Migration 1 -> 2: Remove all meta_* columns
if (currentVersion < 2) {
console.log('[Migration] Running migration v2: Removing meta columns...');
dropColumnIfExists(db, 'ai_messages_history', 'meta_message');
dropColumnIfExists(db, 'book_act_summaries', 'meta_acts');
dropColumnIfExists(db, 'book_ai_guide_line', 'meta');
dropColumnIfExists(db, 'book_chapters', 'meta_chapter');
dropColumnIfExists(db, 'book_chapter_content', 'meta_chapter_content');
dropColumnIfExists(db, 'book_chapter_infos', 'meta_chapter_info');
dropColumnIfExists(db, 'book_characters', 'char_meta');
dropColumnIfExists(db, 'book_characters_attributes', 'attr_meta');
dropColumnIfExists(db, 'book_guide_line', 'meta_guide_line');
dropColumnIfExists(db, 'book_incidents', 'meta_incident');
dropColumnIfExists(db, 'book_issues', 'meta_issue');
dropColumnIfExists(db, 'book_location', 'loc_meta');
dropColumnIfExists(db, 'book_plot_points', 'meta_plot');
dropColumnIfExists(db, 'book_world', 'meta_world');
dropColumnIfExists(db, 'book_world_elements', 'meta_element');
dropColumnIfExists(db, 'erit_books', 'book_meta');
dropColumnIfExists(db, 'erit_users', 'user_meta');
dropColumnIfExists(db, 'location_element', 'element_meta');
dropColumnIfExists(db, 'location_sub_element', 'sub_elem_meta');
console.log('[Migration] Migration v2 completed');
}
// Update schema version
setDbSchemaVersion(db, SCHEMA_VERSION);
console.log(`[Migration] Schema updated to version ${SCHEMA_VERSION}`);
}

View File

@@ -4,9 +4,9 @@ import Chapter from '../database/models/Chapter.js';
import type { ChapterProps, CompanionContent, ActStory } from '../database/models/Chapter.js';
interface GetWholeChapterData {
chapterId: string;
id: string;
version: number;
bookId?: string;
bookid: string;
}
interface SaveChapterContentData {
@@ -53,7 +53,7 @@ ipcMain.handle('db:book:chapters', createHandler<string, ChapterProps[]>(
// GET /chapter/whole - Get whole chapter
ipcMain.handle('db:chapter:whole', createHandler<GetWholeChapterData, ChapterProps>(
function(userId: string, data: GetWholeChapterData, lang: 'fr' | 'en'): ChapterProps {
return Chapter.getWholeChapter(userId, data.chapterId, data.version, data.bookId, lang);
return Chapter.getWholeChapter(userId, data.id, data.version, data.bookid, lang);
}
)
);