Extend repository methods and synchronization logic with existence checks for books, chapters, characters, and related entities

- Add methods to verify the existence of acts, plot points, incidents, worlds, characters, locations, and their sub-elements in repositories.
- Update `syncBookFromServerToClient` to integrate existence checks with insert or update logic.
- Enhance multilingual error handling across repository methods.
This commit is contained in:
natreex
2025-12-24 11:48:50 -05:00
parent 6d661806d4
commit 4bc6a40b38
5 changed files with 356 additions and 50 deletions

View File

@@ -1968,6 +1968,7 @@ export default class Book {
static async syncBookFromServerToClient(userId:string,completeBook: CompleteBook,lang:"fr"|"en"):Promise<boolean> {
const userKey: string = getUserEncryptionKey(userId);
const actSummaries: BookActSummariesTable[] = completeBook.actSummaries;
const chapters: BookChaptersTable[] = completeBook.chapters;
const plotPoints: BookPlotPointsTable[] = completeBook.plotPoints;
@@ -1984,24 +1985,39 @@ export default class Book {
const issues: BookIssuesTable[] = completeBook.issues;
const bookId: string = completeBook.eritBooks.length > 0 ? completeBook.eritBooks[0].book_id : '';
if (actSummaries && actSummaries.length > 0) {
for (const actSummary of actSummaries) {
const summary: string = System.encryptDataWithUserKey(actSummary.summary ? actSummary.summary : '', userKey)
const updated: boolean = BookRepo.updateActSummary(userId, bookId, actSummary.act_index, summary, actSummary.last_update, lang);
if (!updated) {
return false;
}
}
}
if (chapters && chapters.length > 0) {
for (const chapter of chapters) {
const isExist: boolean = ChapterRepo.isChapterExist(userId, chapter.chapter_id,lang);
const title: string = System.encryptDataWithUserKey(chapter.title, userKey)
if (isExist) {
const updated: boolean = ChapterRepo.updateChapter(userId, chapter.chapter_id, title, chapter.hashed_title, chapter.chapter_order, chapter.last_update, lang);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncChapter(chapter.chapter_id, chapter.book_id, userId, title, chapter.hashed_title, chapter.words_count || 0, chapter.chapter_order, chapter.last_update,lang);
if (!insert) {
return false;
}
}
}
}
if (actSummaries && actSummaries.length > 0) {
for (const act of actSummaries) {
const isExist: boolean = BookRepo.actSummarizeExist(userId, bookId, act.act_index,lang);
const summary: string = System.encryptDataWithUserKey(act.summary ? act.summary : '', userKey)
if (isExist) {
const updated: boolean = BookRepo.updateActSummary(userId, bookId, act.act_index, summary, act.last_update,lang);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncActSummary(act.act_sum_id, userId, bookId, act.act_index, summary, act.last_update,lang);
if (!insert) {
return false;
}
}
}
}
@@ -2009,10 +2025,21 @@ export default class Book {
for (const plotPoint of plotPoints) {
const title: string = System.encryptDataWithUserKey(plotPoint.title, userKey);
const summary: string = System.encryptDataWithUserKey(plotPoint.summary ? plotPoint.summary : '', userKey);
const ifExist: boolean = BookRepo.plotPointExist(userId, bookId, plotPoint.plot_point_id,lang);
if (ifExist) {
const updated: boolean = BookRepo.updatePlotPoint(userId, bookId, plotPoint.plot_point_id, title, plotPoint.hashed_title, summary, plotPoint.last_update,lang);
if (!updated) {
return false;
}
} else {
if (!plotPoint.linked_incident_id) {
return false;
}
const created: boolean = BookRepo.insertSyncPlotPoint(plotPoint.plot_point_id, title, plotPoint.hashed_title, summary, plotPoint.linked_incident_id, plotPoint.author_id, bookId, plotPoint.last_update,lang);
if (!created) {
return false;
}
}
}
}
@@ -2020,17 +2047,25 @@ export default class Book {
for (const incident of incidents) {
const title: string = System.encryptDataWithUserKey(incident.title, userKey);
const summary: string = System.encryptDataWithUserKey(incident.summary ? incident.summary : '', userKey);
const isExist: boolean = BookRepo.incidentExist(userId, bookId, incident.incident_id,lang);
if (isExist) {
const updated: boolean = BookRepo.updateIncident(userId, bookId, incident.incident_id, title, incident.hashed_title, summary, incident.last_update,lang);
if (!updated) {
return false;
}
} else {
const created: boolean = BookRepo.insertSyncIncident(incident.incident_id, userId, bookId, title, incident.hashed_title, summary, incident.last_update,lang);
if (!created) {
return false;
}
}
}
}
if (chapterContents && chapterContents.length > 0) {
for (const chapterContent of chapterContents) {
const content: string = System.encryptDataWithUserKey(chapterContent.content ? JSON.stringify(chapterContent.content) : '', userKey);
const updated: boolean = ChapterRepo.updateChapterContent(userId, chapterContent.chapter_id, chapterContent.version, content, chapterContent.words_count, chapterContent.last_update, lang);
const updated: boolean = ChapterRepo.updateChapterContent(userId, chapterContent.chapter_id, chapterContent.version, content, chapterContent.words_count, chapterContent.last_update);
if (!updated) {
return false;
}
@@ -2039,107 +2074,180 @@ export default class Book {
if (chapterInfos && chapterInfos.length > 0) {
for (const chapterInfo of chapterInfos) {
const isExist: boolean = ChapterRepo.isChapterInfoExist(userId, chapterInfo.chapter_id,lang);
const summary: string = System.encryptDataWithUserKey(chapterInfo.summary ? chapterInfo.summary : '', userKey);
const goal: string = System.encryptDataWithUserKey(chapterInfo.goal ? chapterInfo.goal : '', userKey);
if (isExist) {
const updated: boolean = ChapterRepo.updateChapterInfos(userId, chapterInfo.chapter_id, chapterInfo.act_id, bookId, chapterInfo.incident_id, chapterInfo.plot_point_id, summary, goal, chapterInfo.last_update,lang);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncChapterInfo(chapterInfo.chapter_info_id, chapterInfo.chapter_id, chapterInfo.act_id, chapterInfo.incident_id, chapterInfo.plot_point_id, bookId, chapterInfo.author_id, chapterInfo.summary, chapterInfo.goal, chapterInfo.last_update,lang);
if (!insert) {
return false;
}
}
}
}
if (characters && characters.length > 0) {
for (const character of characters) {
const isExist: boolean = CharacterRepo.isCharacterExist(userId, character.character_id,lang);
const firstName: string = System.encryptDataWithUserKey(character.first_name, userKey);
const lastName: string = System.encryptDataWithUserKey(character.last_name ? character.last_name : '', userKey);
const category: string = System.encryptDataWithUserKey(character.category, userKey);
const title: string = System.encryptDataWithUserKey(character.title ? character.title : '', userKey);
const role: string = System.encryptDataWithUserKey(character.role ? character.role : '', userKey);
const image: string = System.encryptDataWithUserKey(character.image ? character.image : '', userKey);
const biography: string = System.encryptDataWithUserKey(character.biography ? character.biography : '', userKey);
const history: string = System.encryptDataWithUserKey(character.history ? character.history : '', userKey);
const updated: boolean = CharacterRepo.updateCharacter(userId, character.character_id, firstName, lastName, title, category, character.image || '', role, biography, history, character.last_update, lang);
if (isExist) {
const updated: boolean = CharacterRepo.updateCharacter(userId, character.character_id, firstName, lastName, title, category, image, role, biography, history, character.last_update);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncCharacter(character.character_id, bookId, userId, firstName, lastName, category, title, image, role, biography, history, character.last_update,lang);
if (!insert) {
return false;
}
}
}
}
if (characterAttributes && characterAttributes.length > 0) {
for (const characterAttribute of characterAttributes) {
const isExist: boolean = CharacterRepo.isCharacterAttributeExist(userId, characterAttribute.attr_id,lang);
const attributeName: string = System.encryptDataWithUserKey(characterAttribute.attribute_name, userKey);
const attributeValue: string = System.encryptDataWithUserKey(characterAttribute.attribute_value, userKey);
if (isExist) {
const updated: boolean = CharacterRepo.updateCharacterAttribute(userId, characterAttribute.attr_id, attributeName, attributeValue, characterAttribute.last_update,lang);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncCharacterAttribute(characterAttribute.attr_id, characterAttribute.character_id, userId, attributeName, attributeValue, characterAttribute.last_update,lang);
if (!insert) {
return false;
}
}
}
}
if (locations && locations.length > 0) {
for (const location of locations) {
const isExist: boolean = LocationRepo.isLocationExist(userId, location.loc_id,lang);
const locName: string = System.encryptDataWithUserKey(location.loc_name, userKey);
if (isExist) {
const updated: boolean = LocationRepo.updateLocationSection(userId, location.loc_id, locName, location.loc_original_name, location.last_update,lang);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncLocation(location.loc_id, bookId, userId, locName, location.loc_original_name, location.last_update,lang);
if (!insert) {
return false;
}
}
}
}
if (locationElements && locationElements.length > 0) {
for (const locationElement of locationElements) {
const isExist: boolean = LocationRepo.isLocationElementExist(userId, locationElement.element_id,lang);
const elementName: string = System.encryptDataWithUserKey(locationElement.element_name, userKey);
const elementDescription: string = System.encryptDataWithUserKey(locationElement.element_description ? locationElement.element_description : '', userKey);
if (isExist) {
const updated: boolean = LocationRepo.updateLocationElement(userId, locationElement.element_id, elementName, locationElement.original_name, elementDescription, locationElement.last_update,lang);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncLocationElement(locationElement.element_id, locationElement.location, userId, elementName, locationElement.original_name, elementDescription, locationElement.last_update,lang);
if (!insert) {
return false;
}
}
}
}
if (locationSubElements && locationSubElements.length > 0) {
for (const locationSubElement of locationSubElements) {
const isExist: boolean = LocationRepo.isLocationSubElementExist(userId, locationSubElement.sub_element_id,lang);
const subElemName: string = System.encryptDataWithUserKey(locationSubElement.sub_elem_name, userKey);
const subElemDescription: string = System.encryptDataWithUserKey(locationSubElement.sub_elem_description ? locationSubElement.sub_elem_description : '', userKey);
if (isExist) {
const updated: boolean = LocationRepo.updateLocationSubElement(userId, locationSubElement.sub_element_id, subElemName, locationSubElement.original_name, subElemDescription, locationSubElement.last_update,lang);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncLocationSubElement(locationSubElement.sub_element_id, locationSubElement.element_id, userId, subElemName, locationSubElement.original_name, subElemDescription, locationSubElement.last_update,lang);
if (!insert) {
return false;
}
}
}
}
if (worlds && worlds.length > 0) {
for (const world of worlds) {
const isExist: boolean = BookRepo.worldExist(userId, bookId, world.world_id,lang);
const name: string = System.encryptDataWithUserKey(world.name, userKey);
const history: string = System.encryptDataWithUserKey(world.history ? world.history : '', userKey);
const politics: string = System.encryptDataWithUserKey(world.politics ? world.politics : '', userKey);
const economy: string = System.encryptDataWithUserKey(world.economy ? world.economy : '', userKey);
const religion: string = System.encryptDataWithUserKey(world.religion ? world.religion : '', userKey);
const languages: string = System.encryptDataWithUserKey(world.languages ? world.languages : '', userKey);
if (isExist) {
const updated: boolean = BookRepo.updateWorld(userId, world.world_id, name, world.hashed_name, history, politics, economy, religion, languages, world.last_update,lang);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncWorld(world.world_id, name, world.hashed_name, userId, bookId, history, politics, economy, religion, languages, world.last_update,lang);
if (!insert) {
return false;
}
}
}
}
if (worldElements && worldElements.length > 0) {
for (const worldElement of worldElements) {
const isExist: boolean = BookRepo.worldElementExist(userId, worldElement.world_id, worldElement.element_id,lang);
const name: string = System.encryptDataWithUserKey(worldElement.name, userKey);
const description: string = System.encryptDataWithUserKey(worldElement.description ? worldElement.description : '', userKey);
if (isExist) {
const updated: boolean = BookRepo.updateWorldElement(userId, worldElement.element_id, name, description, worldElement.last_update,lang);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncWorldElement(worldElement.element_id, worldElement.world_id, userId, worldElement.element_type, name, worldElement.original_name, description, worldElement.last_update,lang);
if (!insert) {
return false;
}
}
}
}
if (issues && issues.length > 0) {
for (const issue of issues) {
const isExist: boolean = BookRepo.issueExist(userId, bookId, issue.issue_id,lang);
const name: string = System.encryptDataWithUserKey(issue.name, userKey);
if (isExist) {
const updated: boolean = BookRepo.updateIssue(userId, bookId, issue.issue_id, name, issue.hashed_issue_name, issue.last_update, lang);
if (!updated) {
return false;
}
} else {
const insert: boolean = BookRepo.insertSyncIssue(issue.issue_id, userId, bookId, name, issue.hashed_issue_name, issue.last_update, lang);
if (!insert) {
return false;
}
}
}
}
return true;

View File

@@ -2352,4 +2352,93 @@ export default class BookRepo {
}
}
}
static actSummarizeExist(userId: string, bookId: string, act_index: number,lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result:QueryResult|null = db.get('SELECT 1 FROM book_act_summaries WHERE user_id =? AND book_id =? AND act_index = ?', [userId, bookId, act_index]) || null;
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 résumé de l'acte.` : `Unable to check act summary existence.`);
} else {
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static plotPointExist(userId: string, bookId: string, plot_point_id: string,lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result:QueryResult|null = db.get('SELECT 1 FROM book_plot_points WHERE author_id =? AND book_id =? AND plot_point_id =?', [userId, bookId, plot_point_id]) || null;
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 point de intrigue.` : `Unable to check plot point existence.`);
} else {
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static incidentExist(userId: string, bookId: string, incident_id: string,lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM book_incidents WHERE book_id=? AND incident_id=? AND author_id=?', [bookId, incident_id, userId]) || null;
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'incident.` : `Unable to check incident existence.`);
} else {
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static worldExist(userId: string, bookId: string, world_id: string,lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM `book_world` WHERE `world_id`=? AND `author_id`=? AND `book_id`=?', [world_id, userId, bookId]) || null;
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 check world existence.`);
} else {
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static worldElementExist(userId: string, world_id: string, element_id: string, lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM `book_world_elements` WHERE `element_id`=? AND `world_id`=? AND `user_id`=?', [element_id, world_id, userId]) || null;
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 du monde.` : `Unable to check world element existence.`);
} else {
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static issueExist(userId: string, bookId: string, issue_id: string,lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM `book_issues` WHERE `issue_id`=? AND `author_id`=? AND `book_id`=?', [issue_id, userId, bookId]) || null;
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 problème.` : `Unable to check issue existence.`);
} else {
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
}

View File

@@ -1,4 +1,4 @@
import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import {Database, QueryResult, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import System from "../System.js";
export interface ChapterContentQueryResult extends Record<string, SQLiteValue>{
@@ -410,4 +410,35 @@ export default class ChapterRepo{
}
}
}
static isChapterExist(userId: string, chapter_id: string, lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM book_chapters WHERE chapter_id=? AND author_id=?', [chapter_id, userId]) || null;
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 chapitre.` : `Unable to check chapter existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static isChapterInfoExist(userId: string, chapter_id: string, lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM `book_chapter_infos` WHERE `chapter_id`=? AND `author_id`=?', [chapter_id, userId]) || null;
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 des informations du chapitre.` : `Unable to check chapter info existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
}

View File

@@ -1,4 +1,4 @@
import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import {Database, QueryResult, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import System from "../System.js";
export interface CharacterResult extends Record<string, SQLiteValue> {
@@ -180,4 +180,35 @@ export default class CharacterRepo {
}
}
}
static isCharacterExist(userId: string, characterId: string,lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM `book_characters` WHERE `character_id`=? AND `user_id`=?', [characterId, userId]) || null;
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 personnage.` : `Unable to check character existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static isCharacterAttributeExist(userId: string, characterAttributeId: string,lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM `book_characters_attributes` WHERE `attr_id`=? AND `user_id`=?', [characterAttributeId, userId]) || null;
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'attribut du personnage.` : `Unable to check character attribute existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
}

View File

@@ -1,4 +1,4 @@
import {Database, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import {Database, QueryResult, RunResult, SQLiteValue} from 'node-sqlite3-wasm';
import System from "../System.js";
export interface LocationQueryResult extends Record<string, SQLiteValue> {
@@ -255,4 +255,51 @@ export default class LocationRepo {
}
return result;
}
static isLocationExist(userId: string, locId: string,lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM `book_location` WHERE `loc_id`=? AND `user_id`=?', [locId, userId]) || null;
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'emplacement.` : `Unable to check location existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static isLocationElementExist(userId: string, elementId: string,lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM `location_element` WHERE `element_id`=? AND `user_id`=?', [elementId, userId]) || null;
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 d'emplacement.` : `Unable to check location element existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
static isLocationSubElementExist(userId: string, subElementId: string,lang: "fr" | "en"): boolean {
try {
const db: Database = System.getDb();
const result: QueryResult | null = db.get('SELECT 1 FROM `location_sub_element` WHERE `sub_element_id`=? AND `user_id`=?', [subElementId, userId]) || null;
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 sous-élément d'emplacement.` : `Unable to check location sub-element existence.`);
} else {
console.error("An unknown error occurred.");
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
}
}
}
}