Remove CharacterComponent and CharacterDetail components
- Deleted `CharacterComponent` and `CharacterDetail` files from the project. - Refactored related logic to improve code maintainability and reduce redundancy.
This commit is contained in:
@@ -36,6 +36,7 @@ import { SyncedActSummary } from "./Act.js";
|
||||
import { SyncedAIGuideLine, SyncedGuideLine } from "./GuideLine.js";
|
||||
import Cover from "./Cover.js";
|
||||
import UserRepo from "../repositories/user.repository.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export interface SyncedBookTools {
|
||||
lastUpdate: number;
|
||||
@@ -369,6 +370,7 @@ export interface SyncedSeriesSpellTag {
|
||||
export interface SyncedSeries {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
lastUpdate: number;
|
||||
books: SyncedSeriesBook[];
|
||||
characters: SyncedSeriesCharacter[];
|
||||
@@ -500,6 +502,8 @@ export default class Book {
|
||||
const book: Book = new Book(bookId);
|
||||
book.getBookInfos(userId);
|
||||
const bookTools: BookToolsTable | null = BookRepo.fetchBookTools(userId, bookId, lang);
|
||||
// Récupérer le seriesId depuis series_books
|
||||
const seriesId: string | null = BookRepo.fetchBookSeriesId(bookId, lang);
|
||||
return {
|
||||
bookId: book.getId(),
|
||||
type: book.getType(),
|
||||
@@ -508,6 +512,7 @@ export default class Book {
|
||||
subTitle: book.getSubTitle(),
|
||||
summary: book.getSummary(),
|
||||
serieId: book.getSerieId(),
|
||||
seriesId: seriesId,
|
||||
desiredReleaseDate: book.getDesiredReleaseDate(),
|
||||
desiredWordCount: book.getDesiredWordCount(),
|
||||
wordCount: book.getWordCount(),
|
||||
@@ -547,11 +552,16 @@ export default class Book {
|
||||
* Removes a book from the database.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param bookId - The unique identifier of the book to remove
|
||||
* @param deletedAt - The timestamp of deletion (from UI via System.timeStampInSeconds())
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the book was removed, false otherwise
|
||||
*/
|
||||
public static removeBook(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return BookRepo.deleteBook(userId, bookId, lang);
|
||||
public static removeBook(userId: string, bookId: string, deletedAt: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = BookRepo.deleteBook(userId, bookId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'erit_books', bookId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public static updateBookToolSetting(userId: string, bookId: string, toolName: 'characters' | 'worlds' | 'locations' | 'spells', enabled: boolean, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
|
||||
@@ -13,6 +13,7 @@ import ChapterContentRepository, {
|
||||
CompanionContentQueryResult,
|
||||
ContentQueryResult
|
||||
} from "../repositories/chaptercontent.repository.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export interface ChapterContent {
|
||||
version: number;
|
||||
@@ -262,12 +263,18 @@ export default class Chapter {
|
||||
/**
|
||||
* Removes a chapter from the database.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param bookId - The unique identifier of the book
|
||||
* @param chapterId - The unique identifier of the chapter to remove
|
||||
* @param deletedAt - The timestamp of deletion (from UI via System.timeStampInSeconds())
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the chapter was removed successfully, false otherwise
|
||||
*/
|
||||
public static removeChapter(userId: string, chapterId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return ChapterRepo.deleteChapter(userId, chapterId, lang);
|
||||
public static removeChapter(userId: string, bookId: string, chapterId: string, deletedAt: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = ChapterRepo.deleteChapter(userId, chapterId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_chapters', chapterId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -437,12 +444,18 @@ export default class Chapter {
|
||||
/**
|
||||
* Removes chapter information by its identifier.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param bookId - The unique identifier of the book
|
||||
* @param chapterInfoId - The unique identifier of the chapter information to remove
|
||||
* @param deletedAt - The timestamp of deletion (from UI via System.timeStampInSeconds())
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the chapter information was removed successfully, false otherwise
|
||||
*/
|
||||
static removeChapterInformation(userId: string, chapterInfoId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return ChapterRepo.deleteChapterInformation(userId, chapterInfoId, lang);
|
||||
static removeChapterInformation(userId: string, bookId: string, chapterInfoId: string, deletedAt: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = ChapterRepo.deleteChapterInformation(userId, chapterInfoId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_chapter_infos', chapterInfoId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ import CharacterRepo, {
|
||||
import BookRepo, {BookToolsTable} from "../repositories/book.repository.js";
|
||||
import System from "../System.js";
|
||||
import {getUserEncryptionKey} from "../keyManager.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export type CharacterCategory = 'Main' | 'Secondary' | 'Recurring';
|
||||
|
||||
@@ -290,23 +291,35 @@ export default class Character {
|
||||
/**
|
||||
* Deletes an attribute from a character.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param bookId - The unique identifier of the book
|
||||
* @param attributeId - The unique identifier of the attribute to delete
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language code for localization (defaults to 'fr')
|
||||
* @returns True if the deletion was successful, false otherwise
|
||||
*/
|
||||
static deleteAttribute(userId: string, attributeId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return CharacterRepo.deleteAttribute(userId, attributeId, lang);
|
||||
static deleteAttribute(userId: string, bookId: string, attributeId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = CharacterRepo.deleteAttribute(userId, attributeId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_characters_attributes', attributeId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a character and all its related data.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param bookId - The unique identifier of the book
|
||||
* @param characterId - The unique identifier of the character to delete
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language code for localization (defaults to 'fr')
|
||||
* @returns True if the deletion was successful
|
||||
*/
|
||||
static deleteCharacter(userId: string, characterId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return CharacterRepo.deleteCharacter(userId, characterId, lang);
|
||||
static deleteCharacter(userId: string, bookId: string, characterId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = CharacterRepo.deleteCharacter(userId, characterId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_characters', characterId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -235,9 +235,9 @@ export default class Download {
|
||||
|
||||
const spellsInserted: boolean = data.spells.every((spell: BookSpellsTable): boolean => {
|
||||
const encryptedName: string = System.encryptDataWithUserKey(spell.name, userEncryptionKey);
|
||||
const encryptedDescription: string = System.encryptDataWithUserKey(spell.description, userEncryptionKey);
|
||||
const encryptedAppearance: string = System.encryptDataWithUserKey(spell.appearance, userEncryptionKey);
|
||||
const encryptedTags: string = System.encryptDataWithUserKey(spell.tags, userEncryptionKey);
|
||||
const encryptedDescription: string | null = spell.description ? System.encryptDataWithUserKey(spell.description, userEncryptionKey) : null;
|
||||
const encryptedAppearance: string | null = spell.appearance ? System.encryptDataWithUserKey(spell.appearance, userEncryptionKey) : null;
|
||||
const encryptedTags: string | null = spell.tags ? System.encryptDataWithUserKey(spell.tags, userEncryptionKey) : null;
|
||||
const encryptedPowerLevel: string | null = spell.power_level ? System.encryptDataWithUserKey(spell.power_level, userEncryptionKey) : null;
|
||||
const encryptedComponents: string | null = spell.components ? System.encryptDataWithUserKey(spell.components, userEncryptionKey) : null;
|
||||
const encryptedLimitations: string | null = spell.limitations ? System.encryptDataWithUserKey(spell.limitations, userEncryptionKey) : null;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getUserEncryptionKey } from "../keyManager.js";
|
||||
import System from "../System.js";
|
||||
import { ActChapter } from "./Act.js";
|
||||
import IncidentRepository, { IncidentQuery } from "../repositories/incident.repository.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export interface IncidentStory {
|
||||
incidentTitle: string;
|
||||
@@ -91,6 +92,7 @@ export default class Incident {
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param bookId - The unique identifier of the book
|
||||
* @param incidentId - The unique identifier of the incident to remove
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages (defaults to 'fr')
|
||||
* @returns True if the incident was successfully deleted, false otherwise
|
||||
*/
|
||||
@@ -98,8 +100,13 @@ export default class Incident {
|
||||
userId: string,
|
||||
bookId: string,
|
||||
incidentId: string,
|
||||
deletedAt: number = System.timeStampInSeconds(),
|
||||
lang: 'fr' | 'en' = 'fr'
|
||||
): boolean {
|
||||
return IncidentRepository.deleteIncident(userId, bookId, incidentId, lang);
|
||||
const deleted: boolean = IncidentRepository.deleteIncident(userId, bookId, incidentId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_incidents', incidentId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getUserEncryptionKey } from "../keyManager.js";
|
||||
import System from "../System.js";
|
||||
import IssueRepository, { IssueQuery } from "../repositories/issue.repository.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
/**
|
||||
* Represents a synced issue with its metadata.
|
||||
@@ -84,15 +85,23 @@ export default class Issue {
|
||||
* Removes an issue from the database.
|
||||
*
|
||||
* @param userId - The unique identifier of the user.
|
||||
* @param bookId - The unique identifier of the book.
|
||||
* @param issueId - The unique identifier of the issue to remove.
|
||||
* @param deletedAt - The timestamp of deletion.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns True if the issue was successfully removed, false otherwise.
|
||||
*/
|
||||
public static removeIssue(
|
||||
userId: string,
|
||||
bookId: string,
|
||||
issueId: string,
|
||||
deletedAt: number = System.timeStampInSeconds(),
|
||||
lang: 'fr' | 'en' = 'fr'
|
||||
): boolean {
|
||||
return IssueRepository.deleteIssue(userId, issueId, lang);
|
||||
const deleted: boolean = IssueRepository.deleteIssue(userId, issueId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_issues', issueId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import LocationRepo, {
|
||||
import System from "../System.js";
|
||||
import {getUserEncryptionKey} from "../keyManager.js";
|
||||
import BookRepo, {BookToolsTable} from "../repositories/book.repository.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export interface SubElement {
|
||||
id: string;
|
||||
@@ -229,34 +230,52 @@ export default class Location {
|
||||
/**
|
||||
* Deletes a location section and all its associated elements and sub-elements.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param bookId - The book's unique identifier.
|
||||
* @param locationId - The location's unique identifier to delete.
|
||||
* @param deletedAt - The timestamp of deletion.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns The result of the delete operation.
|
||||
*/
|
||||
static deleteLocationSection(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return LocationRepo.deleteLocationSection(userId, locationId, lang);
|
||||
static deleteLocationSection(userId: string, bookId: string, locationId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = LocationRepo.deleteLocationSection(userId, locationId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_location', locationId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a location element and all its associated sub-elements.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param bookId - The book's unique identifier.
|
||||
* @param elementId - The element's unique identifier to delete.
|
||||
* @param deletedAt - The timestamp of deletion.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns The result of the delete operation.
|
||||
*/
|
||||
static deleteLocationElement(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return LocationRepo.deleteLocationElement(userId, elementId, lang);
|
||||
static deleteLocationElement(userId: string, bookId: string, elementId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = LocationRepo.deleteLocationElement(userId, elementId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'location_element', elementId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a location sub-element.
|
||||
* @param userId - The user's unique identifier.
|
||||
* @param bookId - The book's unique identifier.
|
||||
* @param subElementId - The sub-element's unique identifier to delete.
|
||||
* @param deletedAt - The timestamp of deletion.
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns The result of the delete operation.
|
||||
*/
|
||||
static deleteLocationSubElement(userId: string, subElementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return LocationRepo.deleteLocationSubElement(userId, subElementId, lang);
|
||||
static deleteLocationSubElement(userId: string, bookId: string, subElementId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = LocationRepo.deleteLocationSubElement(userId, subElementId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'location_sub_element', subElementId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getUserEncryptionKey } from "../keyManager.js";
|
||||
import System from "../System.js";
|
||||
import { ActChapter } from "./Act.js";
|
||||
import PlotPointRepository, { PlotPointQuery } from "../repositories/plotpoint.repository.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export interface PlotPointStory {
|
||||
plotTitle: string;
|
||||
@@ -96,11 +97,17 @@ export default class PlotPoint {
|
||||
/**
|
||||
* Removes a plot point from the database.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param bookId - The unique identifier of the book
|
||||
* @param plotId - The unique identifier of the plot point to remove
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
|
||||
* @returns True if the plot point was successfully deleted, false otherwise
|
||||
*/
|
||||
static removePlotPoint(userId: string, plotId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return PlotPointRepository.deletePlotPoint(userId, plotId, lang);
|
||||
static removePlotPoint(userId: string, bookId: string, plotId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = PlotPointRepository.deletePlotPoint(userId, plotId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_plot_points', plotId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
}
|
||||
|
||||
41
electron/database/models/RemovedItem.ts
Normal file
41
electron/database/models/RemovedItem.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import System from '../System.js';
|
||||
import RemovedItemsRepository from '../repositories/removed-items.repository.js';
|
||||
|
||||
/**
|
||||
* Model class for tracking deleted items for sync purposes.
|
||||
* Provides the main entry point for recording deletions.
|
||||
*/
|
||||
export default class RemovedItem {
|
||||
/**
|
||||
* Records a deleted item for sync tracking.
|
||||
* Must be called BEFORE the actual deletion from the source table.
|
||||
*
|
||||
* @param userId - The unique identifier of the user.
|
||||
* @param bookId - The book ID (null for series items).
|
||||
* @param tableName - The name of the table from which the item is deleted.
|
||||
* @param entityId - The UUID of the deleted entity.
|
||||
* @param deletedAt - The timestamp of deletion (from UI via System.timeStampInSeconds()).
|
||||
* @param lang - The language for error messages ('fr' or 'en'). Defaults to 'fr'.
|
||||
* @returns True if the record was inserted successfully.
|
||||
*/
|
||||
public static deleteTracker(
|
||||
userId: string,
|
||||
bookId: string | null,
|
||||
tableName: string,
|
||||
entityId: string,
|
||||
deletedAt: number,
|
||||
lang: 'fr' | 'en' = 'fr'
|
||||
): boolean {
|
||||
const removalId: string = System.createUniqueId();
|
||||
|
||||
return RemovedItemsRepository.insert(
|
||||
removalId,
|
||||
tableName,
|
||||
entityId,
|
||||
bookId,
|
||||
userId,
|
||||
deletedAt,
|
||||
lang
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getUserEncryptionKey } from "../keyManager.js";
|
||||
import System from "../System.js";
|
||||
import SeriesRepo, { SeriesBookResult, SeriesListItem, SeriesResult } from "../repositories/series.repo.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export interface SeriesProps {
|
||||
id: string;
|
||||
@@ -148,16 +149,21 @@ export default class Series {
|
||||
* Deletes a series.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param seriesId - The unique identifier of the series
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the deletion was successful
|
||||
*/
|
||||
public static deleteSeries(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
public static deleteSeries(userId: string, seriesId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
|
||||
if (!exists) {
|
||||
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
|
||||
}
|
||||
|
||||
return SeriesRepo.deleteSeries(userId, seriesId, lang);
|
||||
const deleted: boolean = SeriesRepo.deleteSeries(userId, seriesId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, null, 'book_series', seriesId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,16 +189,21 @@ export default class Series {
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param seriesId - The unique identifier of the series
|
||||
* @param bookId - The unique identifier of the book
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the removal was successful
|
||||
*/
|
||||
public static removeBookFromSeries(userId: string, seriesId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
public static removeBookFromSeries(userId: string, seriesId: string, bookId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const exists: boolean = SeriesRepo.isSeriesExist(userId, seriesId, lang);
|
||||
if (!exists) {
|
||||
throw new Error(lang === 'fr' ? 'Série non trouvée.' : 'Series not found.');
|
||||
}
|
||||
|
||||
return SeriesRepo.removeBookFromSeries(seriesId, bookId, lang);
|
||||
const deleted: boolean = SeriesRepo.removeBookFromSeries(seriesId, bookId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, null, 'series_books', `${seriesId}_${bookId}`, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getUserEncryptionKey } from "../keyManager.js";
|
||||
import System from "../System.js";
|
||||
import SeriesCharacterRepo, { SeriesCharacterAttributeResult, SeriesCharacterResult } from "../repositories/series-character.repo.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export type CharacterCategory = 'Main' | 'Secondary' | 'Recurring';
|
||||
|
||||
@@ -213,15 +214,20 @@ export default class SeriesCharacter {
|
||||
* Deletes a character from a series.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param characterId - The unique identifier of the character
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the deletion was successful
|
||||
*/
|
||||
public static deleteCharacter(userId: string, characterId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
public static deleteCharacter(userId: string, characterId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const exists: boolean = SeriesCharacterRepo.isCharacterExist(userId, characterId, lang);
|
||||
if (!exists) {
|
||||
throw new Error(lang === 'fr' ? 'Personnage non trouvé.' : 'Character not found.');
|
||||
}
|
||||
return SeriesCharacterRepo.deleteCharacter(userId, characterId, lang);
|
||||
const deleted: boolean = SeriesCharacterRepo.deleteCharacter(userId, characterId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, null, 'series_characters', characterId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,11 +254,16 @@ export default class SeriesCharacter {
|
||||
* Deletes an attribute from a character.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param attributeId - The unique identifier of the attribute
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the deletion was successful
|
||||
*/
|
||||
public static deleteAttribute(userId: string, attributeId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return SeriesCharacterRepo.deleteAttribute(userId, attributeId, lang);
|
||||
public static deleteAttribute(userId: string, attributeId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = SeriesCharacterRepo.deleteAttribute(userId, attributeId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, null, 'series_characters_attributes', attributeId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getUserEncryptionKey } from "../keyManager.js";
|
||||
import System from "../System.js";
|
||||
import SeriesLocationRepo, { SeriesLocationResult, SeriesLocationElementResult, SeriesLocationSubElementResult } from "../repositories/series-location.repo.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export interface SeriesLocationSubElementProps {
|
||||
id: string;
|
||||
@@ -123,32 +124,47 @@ export default class SeriesLocation {
|
||||
* Deletes a location section.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param locationId - The unique identifier of the location
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if successful
|
||||
*/
|
||||
public static deleteLocation(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return SeriesLocationRepo.deleteLocation(userId, locationId, lang);
|
||||
public static deleteLocation(userId: string, locationId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = SeriesLocationRepo.deleteLocation(userId, locationId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, null, 'series_locations', locationId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an element.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param elementId - The unique identifier of the element
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if successful
|
||||
*/
|
||||
public static deleteElement(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return SeriesLocationRepo.deleteElement(userId, elementId, lang);
|
||||
public static deleteElement(userId: string, elementId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = SeriesLocationRepo.deleteElement(userId, elementId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, null, 'series_location_elements', elementId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a sub-element.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param subElementId - The unique identifier of the sub-element
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if successful
|
||||
*/
|
||||
public static deleteSubElement(userId: string, subElementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return SeriesLocationRepo.deleteSubElement(userId, subElementId, lang);
|
||||
public static deleteSubElement(userId: string, subElementId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = SeriesLocationRepo.deleteSubElement(userId, subElementId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, null, 'series_location_sub_elements', subElementId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getUserEncryptionKey } from "../keyManager.js";
|
||||
import System from "../System.js";
|
||||
import SeriesSpellRepo, { SeriesSpellResult, SeriesSpellTagResult } from "../repositories/series-spell.repo.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export interface SeriesSpellTagProps {
|
||||
id: string;
|
||||
@@ -155,11 +156,16 @@ export default class SeriesSpell {
|
||||
* Deletes a spell.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param spellId - The unique identifier of the spell
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if successful
|
||||
*/
|
||||
public static deleteSpell(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return SeriesSpellRepo.deleteSpell(userId, spellId, lang);
|
||||
public static deleteSpell(userId: string, spellId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = SeriesSpellRepo.deleteSpell(userId, spellId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, null, 'series_spells', spellId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,10 +208,15 @@ export default class SeriesSpell {
|
||||
* Deletes a tag.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param tagId - The unique identifier of the tag
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if successful
|
||||
*/
|
||||
public static deleteTag(userId: string, tagId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return SeriesSpellRepo.deleteTag(userId, tagId, lang);
|
||||
public static deleteTag(userId: string, tagId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = SeriesSpellRepo.deleteTag(userId, tagId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, null, 'series_spell_tags', tagId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import { getUserEncryptionKey } from "../keyManager.js";
|
||||
import System from "../System.js";
|
||||
import SeriesWorldRepo, { SeriesWorldResult } from "../repositories/series-world.repo.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export interface SeriesWorldElementProps {
|
||||
id: string;
|
||||
@@ -181,10 +182,15 @@ export default class SeriesWorld {
|
||||
* Deletes an element from a world.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param elementId - The unique identifier of the element
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if successful
|
||||
*/
|
||||
public static deleteElement(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return SeriesWorldRepo.deleteElement(userId, elementId, lang);
|
||||
public static deleteElement(userId: string, elementId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = SeriesWorldRepo.deleteElement(userId, elementId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, null, 'series_world_elements', elementId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import SpellTagRepo, { SpellTagResult } from '../repositories/spelltag.repo.js';
|
||||
import BookRepo, { BookToolsTable } from '../repositories/book.repository.js';
|
||||
import System from '../System.js';
|
||||
import { getUserEncryptionKey } from '../keyManager.js';
|
||||
import RemovedItem from './RemovedItem.js';
|
||||
|
||||
export interface SpellTagProps {
|
||||
id: string;
|
||||
@@ -118,16 +119,16 @@ export default class Spell {
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the deletion was successful
|
||||
*/
|
||||
static deleteSpellTag(userId: string, tagId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
static deleteSpellTag(userId: string, bookId: string, tagId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const userKey: string = getUserEncryptionKey(userId);
|
||||
|
||||
const spells: SpellResult[] = SpellRepo.fetchSpells(userId, bookId, lang);
|
||||
|
||||
for (const spell of spells) {
|
||||
const decryptedTags: string = System.decryptDataWithUserKey(spell.tags, userKey);
|
||||
const decryptedTags: string | null = spell.tags ? System.decryptDataWithUserKey(spell.tags, userKey) : null;
|
||||
let tagsArray: string[] = [];
|
||||
try {
|
||||
tagsArray = JSON.parse(decryptedTags) as string[];
|
||||
tagsArray = decryptedTags ? JSON.parse(decryptedTags) as string[] : [];
|
||||
} catch {
|
||||
tagsArray = [];
|
||||
}
|
||||
@@ -140,7 +141,11 @@ export default class Spell {
|
||||
}
|
||||
|
||||
// Then delete the tag
|
||||
return SpellTagRepo.deleteSpellTag(userId, tagId, lang);
|
||||
const deleted: boolean = SpellTagRepo.deleteSpellTag(userId, tagId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_spell_tags', tagId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,12 +177,12 @@ export default class Spell {
|
||||
|
||||
const spells: SpellListItem[] = spellResults.map((spell: SpellResult): SpellListItem => {
|
||||
const decryptedName: string = System.decryptDataWithUserKey(spell.name, userKey);
|
||||
const decryptedDescription: string = System.decryptDataWithUserKey(spell.description, userKey);
|
||||
const decryptedTags: string = System.decryptDataWithUserKey(spell.tags, userKey);
|
||||
const decryptedDescription: string | null = spell.description ? System.decryptDataWithUserKey(spell.description, userKey) : null;
|
||||
const decryptedTags: string | null = spell.tags ? System.decryptDataWithUserKey(spell.tags, userKey) : null;
|
||||
|
||||
let tagIds: string[];
|
||||
try {
|
||||
tagIds = JSON.parse(decryptedTags) as string[];
|
||||
tagIds = decryptedTags ? JSON.parse(decryptedTags) as string[] : [];
|
||||
} catch {
|
||||
tagIds = [];
|
||||
}
|
||||
@@ -186,9 +191,9 @@ export default class Spell {
|
||||
.map((tagId: string): SpellTagProps | undefined => tagMap.get(tagId))
|
||||
.filter((tag: SpellTagProps | undefined): tag is SpellTagProps => tag !== undefined);
|
||||
|
||||
const truncatedDescription: string = decryptedDescription.length > 150
|
||||
? decryptedDescription.substring(0, 150) + '...'
|
||||
: decryptedDescription;
|
||||
const truncatedDescription: string = decryptedDescription
|
||||
? (decryptedDescription.length > 150 ? decryptedDescription.substring(0, 150) + '...' : decryptedDescription)
|
||||
: '';
|
||||
|
||||
return {
|
||||
id: spell.spell_id,
|
||||
@@ -222,13 +227,13 @@ export default class Spell {
|
||||
}
|
||||
|
||||
const decryptedName: string = System.decryptDataWithUserKey(spell.name, userKey);
|
||||
const decryptedDescription: string = System.decryptDataWithUserKey(spell.description, userKey);
|
||||
const decryptedAppearance: string = System.decryptDataWithUserKey(spell.appearance, userKey);
|
||||
const decryptedTags: string = System.decryptDataWithUserKey(spell.tags, userKey);
|
||||
const decryptedDescription: string | null = spell.description ? System.decryptDataWithUserKey(spell.description, userKey) : null;
|
||||
const decryptedAppearance: string | null = spell.appearance ? System.decryptDataWithUserKey(spell.appearance, userKey) : null;
|
||||
const decryptedTags: string | null = spell.tags ? System.decryptDataWithUserKey(spell.tags, userKey) : null;
|
||||
|
||||
let tagIds: string[];
|
||||
try {
|
||||
tagIds = JSON.parse(decryptedTags) as string[];
|
||||
tagIds = decryptedTags ? JSON.parse(decryptedTags) as string[] : [];
|
||||
} catch {
|
||||
tagIds = [];
|
||||
}
|
||||
@@ -236,8 +241,8 @@ export default class Spell {
|
||||
return {
|
||||
id: spell.spell_id,
|
||||
name: decryptedName,
|
||||
description: decryptedDescription,
|
||||
appearance: decryptedAppearance,
|
||||
description: decryptedDescription || '',
|
||||
appearance: decryptedAppearance || '',
|
||||
tags: tagIds,
|
||||
powerLevel: spell.power_level ? System.decryptDataWithUserKey(spell.power_level, userKey) : null,
|
||||
components: spell.components ? System.decryptDataWithUserKey(spell.components, userKey) : null,
|
||||
@@ -356,11 +361,17 @@ export default class Spell {
|
||||
/**
|
||||
* Deletes a spell.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param bookId - The unique identifier of the book
|
||||
* @param spellId - The unique identifier of the spell
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the deletion was successful
|
||||
*/
|
||||
static deleteSpell(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return SpellRepo.deleteSpell(userId, spellId, lang);
|
||||
static deleteSpell(userId: string, bookId: string, spellId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = SpellRepo.deleteSpell(userId, spellId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_spells', spellId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,40 @@ import { SyncedPlotPoint } from "./PlotPoint.js";
|
||||
import { SyncedIssue } from "./Issue.js";
|
||||
import { SyncedActSummary } from "./Act.js";
|
||||
import { SyncedAIGuideLine, SyncedGuideLine } from "./GuideLine.js";
|
||||
import {
|
||||
SyncedSeries,
|
||||
SyncedSeriesBook,
|
||||
SyncedSeriesCharacter,
|
||||
SyncedSeriesCharacterAttribute,
|
||||
SyncedSeriesWorld,
|
||||
SyncedSeriesWorldElement,
|
||||
SyncedSeriesLocation,
|
||||
SyncedSeriesLocationElement,
|
||||
SyncedSeriesLocationSubElement,
|
||||
SyncedSeriesSpell,
|
||||
SyncedSeriesSpellTag
|
||||
} from "./Book.js";
|
||||
import SeriesRepo, {
|
||||
SyncedSeriesResult,
|
||||
SyncedSeriesBookResult
|
||||
} from "../repositories/series.repo.js";
|
||||
import SeriesCharacterRepo, {
|
||||
SyncedSeriesCharacterResult,
|
||||
SyncedSeriesCharacterAttributeResult
|
||||
} from "../repositories/series-character.repo.js";
|
||||
import SeriesWorldRepo, {
|
||||
SyncedSeriesWorldResult,
|
||||
SyncedSeriesWorldElementResult
|
||||
} from "../repositories/series-world.repo.js";
|
||||
import SeriesLocationRepo, {
|
||||
SyncedSeriesLocationResult,
|
||||
SyncedSeriesLocationElementResult,
|
||||
SyncedSeriesLocationSubElementResult
|
||||
} from "../repositories/series-location.repo.js";
|
||||
import SeriesSpellRepo, {
|
||||
SyncedSeriesSpellResult,
|
||||
SyncedSeriesSpellTagResult
|
||||
} from "../repositories/series-spell.repo.js";
|
||||
|
||||
/**
|
||||
* Handles synchronization operations between local database and remote server.
|
||||
@@ -375,9 +409,9 @@ export default class Sync {
|
||||
decryptedSpells.push({
|
||||
...spellRecord,
|
||||
name: System.decryptDataWithUserKey(spellRecord.name, userEncryptionKey),
|
||||
description: System.decryptDataWithUserKey(spellRecord.description, userEncryptionKey),
|
||||
appearance: System.decryptDataWithUserKey(spellRecord.appearance, userEncryptionKey),
|
||||
tags: System.decryptDataWithUserKey(spellRecord.tags, userEncryptionKey),
|
||||
description: spellRecord.description ? System.decryptDataWithUserKey(spellRecord.description, userEncryptionKey) : null,
|
||||
appearance: spellRecord.appearance ? System.decryptDataWithUserKey(spellRecord.appearance, userEncryptionKey) : null,
|
||||
tags: spellRecord.tags ? System.decryptDataWithUserKey(spellRecord.tags, userEncryptionKey) : null,
|
||||
power_level: spellRecord.power_level ? System.decryptDataWithUserKey(spellRecord.power_level, userEncryptionKey) : null,
|
||||
components: spellRecord.components ? System.decryptDataWithUserKey(spellRecord.components, userEncryptionKey) : null,
|
||||
limitations: spellRecord.limitations ? System.decryptDataWithUserKey(spellRecord.limitations, userEncryptionKey) : null,
|
||||
@@ -823,9 +857,9 @@ export default class Sync {
|
||||
for (const serverSpell of completeBook.spells) {
|
||||
const spellExists: boolean = SpellRepo.isSpellExist(userId, serverSpell.spell_id, lang);
|
||||
const encryptedName: string = System.encryptDataWithUserKey(serverSpell.name, userEncryptionKey);
|
||||
const encryptedDescription: string = System.encryptDataWithUserKey(serverSpell.description, userEncryptionKey);
|
||||
const encryptedAppearance: string = System.encryptDataWithUserKey(serverSpell.appearance, userEncryptionKey);
|
||||
const encryptedTags: string = System.encryptDataWithUserKey(serverSpell.tags, userEncryptionKey);
|
||||
const encryptedDescription: string | null = serverSpell.description ? System.encryptDataWithUserKey(serverSpell.description, userEncryptionKey) : null;
|
||||
const encryptedAppearance: string | null = serverSpell.appearance ? System.encryptDataWithUserKey(serverSpell.appearance, userEncryptionKey) : null;
|
||||
const encryptedTags: string | null = serverSpell.tags ? System.encryptDataWithUserKey(serverSpell.tags, userEncryptionKey) : null;
|
||||
const encryptedPowerLevel: string | null = serverSpell.power_level ? System.encryptDataWithUserKey(serverSpell.power_level, userEncryptionKey) : null;
|
||||
const encryptedComponents: string | null = serverSpell.components ? System.encryptDataWithUserKey(serverSpell.components, userEncryptionKey) : null;
|
||||
const encryptedLimitations: string | null = serverSpell.limitations ? System.encryptDataWithUserKey(serverSpell.limitations, userEncryptionKey) : null;
|
||||
@@ -1113,4 +1147,131 @@ export default class Sync {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// ===== SERIES SYNC METHODS =====
|
||||
|
||||
/**
|
||||
* Retrieves all series for the current user with lightweight structure for sync comparison.
|
||||
* Returns synced series with nested characters, worlds, locations, spells, and spell tags.
|
||||
* All encrypted fields are decrypted before return.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns An array of synced series with all nested entities
|
||||
*/
|
||||
static getSyncedSeries(userId: string, lang: 'fr' | 'en'): SyncedSeries[] {
|
||||
const userEncryptionKey: string = getUserEncryptionKey(userId);
|
||||
|
||||
const allSeries: SyncedSeriesResult[] = SeriesRepo.fetchSyncedSeries(userId, lang);
|
||||
const allSeriesBooks: SyncedSeriesBookResult[] = SeriesRepo.fetchSyncedSeriesBooks(userId, lang);
|
||||
const allCharacters: SyncedSeriesCharacterResult[] = SeriesCharacterRepo.fetchSyncedSeriesCharacters(userId, lang);
|
||||
const allCharacterAttributes: SyncedSeriesCharacterAttributeResult[] = SeriesCharacterRepo.fetchSyncedSeriesCharacterAttributes(userId, lang);
|
||||
const allWorlds: SyncedSeriesWorldResult[] = SeriesWorldRepo.fetchSyncedSeriesWorlds(userId, lang);
|
||||
const allWorldElements: SyncedSeriesWorldElementResult[] = SeriesWorldRepo.fetchSyncedSeriesWorldElements(userId, lang);
|
||||
const allLocations: SyncedSeriesLocationResult[] = SeriesLocationRepo.fetchSyncedSeriesLocations(userId, lang);
|
||||
const allLocationElements: SyncedSeriesLocationElementResult[] = SeriesLocationRepo.fetchSyncedSeriesLocationElements(userId, lang);
|
||||
const allLocationSubElements: SyncedSeriesLocationSubElementResult[] = SeriesLocationRepo.fetchSyncedSeriesLocationSubElements(userId, lang);
|
||||
const allSpells: SyncedSeriesSpellResult[] = SeriesSpellRepo.fetchSyncedSeriesSpells(userId, lang);
|
||||
const allSpellTags: SyncedSeriesSpellTagResult[] = SeriesSpellRepo.fetchSyncedSeriesSpellTags(userId, lang);
|
||||
|
||||
return allSeries.map((series: SyncedSeriesResult): SyncedSeries => {
|
||||
const seriesId: string = series.series_id;
|
||||
|
||||
// Map series books
|
||||
const books: SyncedSeriesBook[] = allSeriesBooks
|
||||
.filter((sb: SyncedSeriesBookResult): boolean => sb.series_id === seriesId)
|
||||
.map((sb: SyncedSeriesBookResult): SyncedSeriesBook => ({
|
||||
bookId: sb.book_id,
|
||||
order: sb.book_order,
|
||||
lastUpdate: sb.last_update
|
||||
}));
|
||||
|
||||
// Map characters with attributes
|
||||
const characters: SyncedSeriesCharacter[] = allCharacters
|
||||
.filter((c: SyncedSeriesCharacterResult): boolean => c.series_id === seriesId)
|
||||
.map((c: SyncedSeriesCharacterResult): SyncedSeriesCharacter => ({
|
||||
id: c.character_id,
|
||||
name: System.decryptDataWithUserKey(c.first_name, userEncryptionKey),
|
||||
lastUpdate: c.last_update,
|
||||
attributes: allCharacterAttributes
|
||||
.filter((a: SyncedSeriesCharacterAttributeResult): boolean => a.character_id === c.character_id)
|
||||
.map((a: SyncedSeriesCharacterAttributeResult): SyncedSeriesCharacterAttribute => ({
|
||||
id: a.attr_id,
|
||||
name: System.decryptDataWithUserKey(a.attribute_name, userEncryptionKey),
|
||||
lastUpdate: a.last_update
|
||||
}))
|
||||
}));
|
||||
|
||||
// Map worlds with elements
|
||||
const worlds: SyncedSeriesWorld[] = allWorlds
|
||||
.filter((w: SyncedSeriesWorldResult): boolean => w.series_id === seriesId)
|
||||
.map((w: SyncedSeriesWorldResult): SyncedSeriesWorld => ({
|
||||
id: w.world_id,
|
||||
name: System.decryptDataWithUserKey(w.name, userEncryptionKey),
|
||||
lastUpdate: w.last_update,
|
||||
elements: allWorldElements
|
||||
.filter((e: SyncedSeriesWorldElementResult): boolean => e.world_id === w.world_id)
|
||||
.map((e: SyncedSeriesWorldElementResult): SyncedSeriesWorldElement => ({
|
||||
id: e.element_id,
|
||||
name: System.decryptDataWithUserKey(e.name, userEncryptionKey),
|
||||
lastUpdate: e.last_update
|
||||
}))
|
||||
}));
|
||||
|
||||
// Map locations with elements and sub-elements
|
||||
const locations: SyncedSeriesLocation[] = allLocations
|
||||
.filter((l: SyncedSeriesLocationResult): boolean => l.series_id === seriesId)
|
||||
.map((l: SyncedSeriesLocationResult): SyncedSeriesLocation => ({
|
||||
id: l.loc_id,
|
||||
name: System.decryptDataWithUserKey(l.loc_name, userEncryptionKey),
|
||||
lastUpdate: l.last_update,
|
||||
elements: allLocationElements
|
||||
.filter((e: SyncedSeriesLocationElementResult): boolean => e.location_id === l.loc_id)
|
||||
.map((e: SyncedSeriesLocationElementResult): SyncedSeriesLocationElement => ({
|
||||
id: e.element_id,
|
||||
name: System.decryptDataWithUserKey(e.element_name, userEncryptionKey),
|
||||
lastUpdate: e.last_update,
|
||||
subElements: allLocationSubElements
|
||||
.filter((se: SyncedSeriesLocationSubElementResult): boolean => se.element_id === e.element_id)
|
||||
.map((se: SyncedSeriesLocationSubElementResult): SyncedSeriesLocationSubElement => ({
|
||||
id: se.sub_element_id,
|
||||
name: System.decryptDataWithUserKey(se.sub_elem_name, userEncryptionKey),
|
||||
lastUpdate: se.last_update
|
||||
}))
|
||||
}))
|
||||
}));
|
||||
|
||||
// Map spells
|
||||
const spells: SyncedSeriesSpell[] = allSpells
|
||||
.filter((s: SyncedSeriesSpellResult): boolean => s.series_id === seriesId)
|
||||
.map((s: SyncedSeriesSpellResult): SyncedSeriesSpell => ({
|
||||
id: s.spell_id,
|
||||
name: System.decryptDataWithUserKey(s.name, userEncryptionKey),
|
||||
lastUpdate: s.last_update
|
||||
}));
|
||||
|
||||
// Map spell tags
|
||||
const spellTags: SyncedSeriesSpellTag[] = allSpellTags
|
||||
.filter((t: SyncedSeriesSpellTagResult): boolean => t.series_id === seriesId)
|
||||
.map((t: SyncedSeriesSpellTagResult): SyncedSeriesSpellTag => ({
|
||||
id: t.tag_id,
|
||||
name: System.decryptDataWithUserKey(t.name, userEncryptionKey),
|
||||
lastUpdate: t.last_update
|
||||
}));
|
||||
|
||||
return {
|
||||
id: seriesId,
|
||||
name: System.decryptDataWithUserKey(series.name, userEncryptionKey),
|
||||
description: series.description
|
||||
? System.decryptDataWithUserKey(series.description, userEncryptionKey)
|
||||
: null,
|
||||
lastUpdate: series.last_update,
|
||||
books,
|
||||
characters,
|
||||
worlds,
|
||||
locations,
|
||||
spells,
|
||||
spellTags
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,9 +261,9 @@ export default class Upload {
|
||||
const spells: BookSpellsTable[] = encryptedSpells.map((spell: BookSpellsTable): BookSpellsTable => ({
|
||||
...spell,
|
||||
name: System.decryptDataWithUserKey(spell.name, userEncryptionKey),
|
||||
description: System.decryptDataWithUserKey(spell.description, userEncryptionKey),
|
||||
appearance: System.decryptDataWithUserKey(spell.appearance, userEncryptionKey),
|
||||
tags: System.decryptDataWithUserKey(spell.tags, userEncryptionKey),
|
||||
description: spell.description ? System.decryptDataWithUserKey(spell.description, userEncryptionKey) : null,
|
||||
appearance: spell.appearance ? System.decryptDataWithUserKey(spell.appearance, userEncryptionKey) : null,
|
||||
tags: spell.tags ? System.decryptDataWithUserKey(spell.tags, userEncryptionKey) : null,
|
||||
power_level: spell.power_level ? System.decryptDataWithUserKey(spell.power_level, userEncryptionKey) : null,
|
||||
components: spell.components ? System.decryptDataWithUserKey(spell.components, userEncryptionKey) : null,
|
||||
limitations: spell.limitations ? System.decryptDataWithUserKey(spell.limitations, userEncryptionKey) : null,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getUserEncryptionKey } from "../keyManager.js";
|
||||
import System from "../System.js";
|
||||
import WorldRepository, { WorldElementValue, WorldQuery } from "../repositories/world.repository.js";
|
||||
import BookRepo, {BookToolsTable} from "../repositories/book.repository.js";
|
||||
import RemovedItem from "./RemovedItem.js";
|
||||
|
||||
export interface SyncedWorld {
|
||||
id: string;
|
||||
@@ -269,12 +270,18 @@ export default class World {
|
||||
/**
|
||||
* Removes an element from a world.
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param bookId - The unique identifier of the book
|
||||
* @param elementId - The unique identifier of the element to remove
|
||||
* @param deletedAt - The timestamp of deletion
|
||||
* @param lang - The language for error messages ('fr' or 'en'), defaults to 'fr'
|
||||
* @returns True if the deletion was successful, false otherwise
|
||||
*/
|
||||
public static removeElementFromWorld(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return WorldRepository.deleteElement(userId, elementId, lang);
|
||||
public static removeElementFromWorld(userId: string, bookId: string, elementId: string, deletedAt: number = System.timeStampInSeconds(), lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
const deleted: boolean = WorldRepository.deleteElement(userId, elementId, lang);
|
||||
if (deleted) {
|
||||
RemovedItem.deleteTracker(userId, bookId, 'book_world_elements', elementId, deletedAt, lang);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ export default class BookRepo {
|
||||
let book: BookQuery;
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = '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=?';
|
||||
const query: string = 'SELECT book_id, author_id, title, summary, sub_title, cover_image, desired_release_date, desired_word_count, words_count, serie_id FROM erit_books WHERE book_id=? AND author_id=?';
|
||||
const params: SQLiteValue[] = [bookId, userId];
|
||||
book = db.get(query, params) as BookQuery;
|
||||
} catch (error: unknown) {
|
||||
@@ -456,4 +456,40 @@ export default class BookRepo {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
|
||||
static isBookExist(userId: string, bookId: string, lang: 'fr' | 'en'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT 1 FROM erit_books WHERE author_id = ? AND book_id = ? LIMIT 1';
|
||||
const params: SQLiteValue[] = [userId, bookId];
|
||||
const result = db.get(query, params);
|
||||
return result !== undefined && result !== null;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the series_id for a book from series_books table.
|
||||
* @param bookId - The book identifier
|
||||
* @param lang - The language for error messages
|
||||
* @returns The series_id or null if book is not in a series
|
||||
*/
|
||||
static fetchBookSeriesId(bookId: string, lang: 'fr' | 'en'): string | null {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT series_id FROM series_books WHERE book_id = ? LIMIT 1';
|
||||
const params: SQLiteValue[] = [bookId];
|
||||
const result = db.get(query, params) as { series_id: string } | undefined;
|
||||
return result?.series_id || null;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
158
electron/database/repositories/removed-items.repository.ts
Normal file
158
electron/database/repositories/removed-items.repository.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { Database, RunResult, SQLiteValue } from 'node-sqlite3-wasm';
|
||||
import System from '../System.js';
|
||||
|
||||
export interface RemovedItemRecord extends Record<string, SQLiteValue> {
|
||||
removal_id: string;
|
||||
table_name: string;
|
||||
entity_id: string;
|
||||
book_id: string | null;
|
||||
user_id: string;
|
||||
deleted_at: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repository for tracking deleted items for sync purposes.
|
||||
*/
|
||||
export default class RemovedItemsRepository {
|
||||
/**
|
||||
* Inserts a removal record into the database.
|
||||
* @param removalId - The unique ID for this removal record.
|
||||
* @param tableName - The name of the table from which the item is deleted.
|
||||
* @param entityId - The UUID of the deleted entity.
|
||||
* @param bookId - Book ID (null for series items).
|
||||
* @param userId - The user ID who owns the item.
|
||||
* @param deletedAt - Timestamp of deletion.
|
||||
* @param lang - The language for error messages ('fr' or 'en').
|
||||
* @returns True if inserted successfully.
|
||||
*/
|
||||
public static insert(
|
||||
removalId: string,
|
||||
tableName: string,
|
||||
entityId: string,
|
||||
bookId: string | null,
|
||||
userId: string,
|
||||
deletedAt: number,
|
||||
lang: 'fr' | 'en'
|
||||
): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = `
|
||||
INSERT INTO removed_items (removal_id, table_name, entity_id, book_id, user_id, deleted_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(table_name, entity_id) DO UPDATE SET deleted_at = excluded.deleted_at
|
||||
`;
|
||||
const params: SQLiteValue[] = [removalId, tableName, entityId, bookId, userId, deletedAt];
|
||||
const result: RunResult = db.run(query, params);
|
||||
return result.changes > 0;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible d'enregistrer la suppression.` : `Unable to record deletion.`);
|
||||
} else {
|
||||
console.error("An unknown error occurred.");
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves deletions since a specific timestamp.
|
||||
* Used to get deletions that occurred since last sync.
|
||||
* @param userId - The user ID.
|
||||
* @param since - Timestamp to get deletions after.
|
||||
* @param lang - The language for error messages ('fr' or 'en').
|
||||
* @returns Array of removed item records.
|
||||
*/
|
||||
public static getDeletionsSince(userId: string, since: number, lang: 'fr' | 'en'): RemovedItemRecord[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT * FROM removed_items WHERE user_id = ? AND deleted_at > ?';
|
||||
const params: SQLiteValue[] = [userId, since];
|
||||
const records: RemovedItemRecord[] = db.all(query, params) as RemovedItemRecord[];
|
||||
return records;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les suppressions.` : `Unable to retrieve deletions.`);
|
||||
} else {
|
||||
console.error("An unknown error occurred.");
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an entity was previously deleted.
|
||||
* @param tableName - The table name.
|
||||
* @param entityId - The entity ID.
|
||||
* @param lang - The language for error messages ('fr' or 'en').
|
||||
* @returns True if the entity was deleted locally.
|
||||
*/
|
||||
public static wasDeleted(tableName: string, entityId: string, lang: 'fr' | 'en'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT 1 FROM removed_items WHERE table_name = ? AND entity_id = ? LIMIT 1';
|
||||
const params: SQLiteValue[] = [tableName, entityId];
|
||||
const result = db.get(query, params);
|
||||
return result !== null;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de vérifier si l'élément a été supprimé.` : `Unable to check if item was deleted.`);
|
||||
} else {
|
||||
console.error("An unknown error occurred.");
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all tracked deletions for a specific book.
|
||||
* @param userId - The user ID.
|
||||
* @param bookId - The book ID.
|
||||
* @param lang - The language for error messages ('fr' or 'en').
|
||||
* @returns Array of removed item records for that book.
|
||||
*/
|
||||
public static getDeletionsForBook(userId: string, bookId: string, lang: 'fr' | 'en'): RemovedItemRecord[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT * FROM removed_items WHERE user_id = ? AND book_id = ?';
|
||||
const params: SQLiteValue[] = [userId, bookId];
|
||||
const records: RemovedItemRecord[] = db.all(query, params) as RemovedItemRecord[];
|
||||
return records;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les suppressions pour ce livre.` : `Unable to retrieve deletions for this book.`);
|
||||
} else {
|
||||
console.error("An unknown error occurred.");
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all deletion records for a user.
|
||||
* WARNING: Only use this when wiping user data completely.
|
||||
* @param userId - The user ID.
|
||||
* @param lang - The language for error messages ('fr' or 'en').
|
||||
* @returns True if cleared successfully.
|
||||
*/
|
||||
public static clearAllForUser(userId: string, lang: 'fr' | 'en'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'DELETE FROM removed_items WHERE user_id = ?';
|
||||
const params: SQLiteValue[] = [userId];
|
||||
const result: RunResult = db.run(query, params);
|
||||
return result.changes > 0;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de supprimer les enregistrements de suppression.` : `Unable to clear deletion records.`);
|
||||
} else {
|
||||
console.error("An unknown error occurred.");
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,4 +469,73 @@ export default class SeriesCharacterRepo {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static fetchCharactersTableForSync(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharactersTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT character_id, series_id, user_id, first_name, last_name, nickname, age, gender, species, nationality, status, title, category, image, role, biography, history, speech_pattern, catchphrase, residence, notes, color, last_update FROM series_characters WHERE series_id = ?';
|
||||
return db.all(query, [seriesId]) as SeriesCharactersTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les personnages pour sync.` : `Unable to retrieve characters for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static fetchCharacterAttributesTableForSync(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharacterAttributesTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT sca.attr_id, sca.character_id, sca.user_id, sca.attribute_name, sca.attribute_value, sca.last_update FROM series_characters_attributes sca INNER JOIN series_characters sc ON sca.character_id = sc.character_id WHERE sc.series_id = ?';
|
||||
return db.all(query, [seriesId]) as SeriesCharacterAttributesTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les attributs pour sync.` : `Unable to retrieve attributes for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all characters for a series (alias for fetchCharacters that returns full table result).
|
||||
*/
|
||||
static fetchSeriesCharacters(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharactersTableResult[] {
|
||||
return this.fetchSeriesCharactersTable(userId, seriesId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all character attributes for a series by series ID.
|
||||
*/
|
||||
static fetchSeriesCharacterAttributesBySeriesId(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesCharacterAttributesTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT sca.attr_id, sca.character_id, sca.user_id, sca.attribute_name, sca.attribute_value, sca.last_update FROM series_characters_attributes sca INNER JOIN series_characters sc ON sca.character_id = sc.character_id WHERE sc.series_id = ? AND sc.user_id = ?';
|
||||
return db.all(query, [seriesId, userId]) as SeriesCharacterAttributesTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les attributs par série.` : `Unable to retrieve attributes by series.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a series character exists (alias for isCharacterExist).
|
||||
*/
|
||||
static seriesCharacterExists(userId: string, characterId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.isCharacterExist(userId, characterId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a series character attribute exists (alias for isAttributeExist).
|
||||
*/
|
||||
static seriesCharacterAttributeExists(userId: string, attrId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.isAttributeExist(userId, attrId, lang);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -620,4 +620,194 @@ export default class SeriesLocationRepo {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static fetchLocationsTableForSync(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT loc_id, series_id, user_id, loc_name, loc_original_name, last_update FROM series_locations WHERE series_id = ?';
|
||||
return db.all(query, [seriesId]) as SeriesLocationsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les lieux pour sync.` : `Unable to retrieve locations for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static fetchLocationElementsTableForSync(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationElementsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT sle.element_id, sle.location_id, sle.user_id, sle.element_name, sle.original_name, sle.element_description, sle.last_update FROM series_location_elements sle INNER JOIN series_locations sl ON sle.location_id = sl.loc_id WHERE sl.series_id = ?';
|
||||
return db.all(query, [seriesId]) as SeriesLocationElementsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de lieu pour sync.` : `Unable to retrieve location elements for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static fetchLocationSubElementsTableForSync(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationSubElementsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT slse.sub_element_id, slse.element_id, slse.user_id, slse.sub_elem_name, slse.original_name, slse.sub_elem_description, slse.last_update FROM series_location_sub_elements slse INNER JOIN series_location_elements sle ON slse.element_id = sle.element_id INNER JOIN series_locations sl ON sle.location_id = sl.loc_id WHERE sl.series_id = ?';
|
||||
return db.all(query, [seriesId]) as SeriesLocationSubElementsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les sous-éléments de lieu pour sync.` : `Unable to retrieve location sub-elements for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all locations for a series (alias for fetchSeriesLocationsTable).
|
||||
*/
|
||||
public static fetchSeriesLocations(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationsTableResult[] {
|
||||
return this.fetchSeriesLocationsTable(userId, seriesId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all location elements for a series by series ID.
|
||||
*/
|
||||
public static fetchSeriesLocationElementsBySeriesId(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationElementsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT sle.element_id, sle.location_id, sle.user_id, sle.element_name, sle.original_name, sle.element_description, sle.last_update FROM series_location_elements sle INNER JOIN series_locations sl ON sle.location_id = sl.loc_id WHERE sl.series_id = ? AND sl.user_id = ?';
|
||||
return db.all(query, [seriesId, userId]) as SeriesLocationElementsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de lieu par série.` : `Unable to retrieve location elements by series.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all location sub-elements for a series by series ID.
|
||||
*/
|
||||
public static fetchSeriesLocationSubElementsBySeriesId(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesLocationSubElementsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT slse.sub_element_id, slse.element_id, slse.user_id, slse.sub_elem_name, slse.original_name, slse.sub_elem_description, slse.last_update FROM series_location_sub_elements slse INNER JOIN series_location_elements sle ON slse.element_id = sle.element_id INNER JOIN series_locations sl ON sle.location_id = sl.loc_id WHERE sl.series_id = ? AND sl.user_id = ?';
|
||||
return db.all(query, [seriesId, userId]) as SeriesLocationSubElementsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les sous-éléments de lieu par série.` : `Unable to retrieve location sub-elements by series.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a series location exists (alias for isLocationExist).
|
||||
*/
|
||||
public static seriesLocationExists(userId: string, locationId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.isLocationExist(userId, locationId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a series location element exists (alias for isLocationElementExist).
|
||||
*/
|
||||
public static seriesLocationElementExists(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.isLocationElementExist(userId, elementId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a series location sub-element exists (alias for isLocationSubElementExist).
|
||||
*/
|
||||
public static seriesLocationSubElementExists(userId: string, subElementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.isLocationSubElementExist(userId, subElementId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a series location for sync (alias with compatible signature).
|
||||
*/
|
||||
public static insertSyncSeriesLocation(locationId: string, seriesId: string, userId: string, locName: string, locOriginalName: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.insertSyncLocation(locationId, seriesId, userId, locName, locOriginalName, lastUpdate, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a series location for sync (without originalName).
|
||||
*/
|
||||
public static updateSyncSeriesLocation(locationId: string, userId: string, locName: string, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'UPDATE series_locations SET loc_name = ?, last_update = ? WHERE loc_id = ? AND user_id = ?';
|
||||
const params: SQLiteValue[] = [locName, lastUpdate, locationId, userId];
|
||||
const updateResult: RunResult = db.run(query, params);
|
||||
return updateResult.changes > 0;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de mettre à jour le lieu série pour sync.` : `Unable to update series location for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a series location element for sync (alias with compatible signature).
|
||||
*/
|
||||
public static insertSyncSeriesLocationElement(elementId: string, locationId: string, userId: string, elementName: string, originalName: string, elementDescription: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.insertSyncLocationElement(elementId, locationId, userId, elementName, originalName, elementDescription, lastUpdate, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a series location element for sync (without originalName).
|
||||
*/
|
||||
public static updateSyncSeriesLocationElement(elementId: string, userId: string, elementName: string, elementDescription: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'UPDATE series_location_elements SET element_name = ?, element_description = ?, last_update = ? WHERE element_id = ? AND user_id = ?';
|
||||
const params: SQLiteValue[] = [elementName, elementDescription, lastUpdate, elementId, userId];
|
||||
const updateResult: RunResult = db.run(query, params);
|
||||
return updateResult.changes > 0;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de mettre à jour l'élément de lieu série pour sync.` : `Unable to update series location element for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a series location sub-element for sync (alias with compatible signature).
|
||||
*/
|
||||
public static insertSyncSeriesLocationSubElement(subElementId: string, elementId: string, userId: string, subElemName: string, originalName: string, subElemDescription: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.insertSyncLocationSubElement(subElementId, elementId, userId, subElemName, originalName, subElemDescription, lastUpdate, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a series location sub-element for sync (without originalName).
|
||||
*/
|
||||
public static updateSyncSeriesLocationSubElement(subElementId: string, userId: string, subElemName: string, subElemDescription: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'UPDATE series_location_sub_elements SET sub_elem_name = ?, sub_elem_description = ?, last_update = ? WHERE sub_element_id = ? AND user_id = ?';
|
||||
const params: SQLiteValue[] = [subElemName, subElemDescription, lastUpdate, subElementId, userId];
|
||||
const updateResult: RunResult = db.run(query, params);
|
||||
return updateResult.changes > 0;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de mettre à jour le sous-élément série pour sync.` : `Unable to update series sub-element for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ export interface SeriesSpellsTableResult extends Record<string, SQLiteValue> {
|
||||
user_id: string;
|
||||
name: string;
|
||||
name_hash: string;
|
||||
description: string;
|
||||
appearance: string;
|
||||
tags: string;
|
||||
description: string | null;
|
||||
appearance: string | null;
|
||||
tags: string | null;
|
||||
power_level: string | null;
|
||||
components: string | null;
|
||||
limitations: string | null;
|
||||
@@ -496,4 +496,152 @@ export default class SeriesSpellRepo {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static fetchSpellsTableForSync(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT spell_id, series_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM series_spells WHERE series_id = ?';
|
||||
return db.all(query, [seriesId]) as SeriesSpellsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les sorts pour sync.` : `Unable to retrieve spells for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static fetchSpellTagsTableForSync(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellTagsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT tag_id, series_id, user_id, name, hashed_name, color, last_update FROM series_spell_tags WHERE series_id = ?';
|
||||
return db.all(query, [seriesId]) as SeriesSpellTagsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les tags de sort pour sync.` : `Unable to retrieve spell tags for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all spells for a series (alias for fetchSeriesSpellsTable).
|
||||
*/
|
||||
static fetchSeriesSpells(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellsTableResult[] {
|
||||
return this.fetchSeriesSpellsTable(userId, seriesId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all spell tags for a series (alias for fetchSeriesSpellTagsTable).
|
||||
*/
|
||||
static fetchSeriesSpellTags(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellTagsTableResult[] {
|
||||
return this.fetchSeriesSpellTagsTable(userId, seriesId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a series spell exists (alias for isSpellExist).
|
||||
*/
|
||||
static seriesSpellExists(userId: string, spellId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.isSpellExist(userId, spellId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a series spell tag exists (alias for isSpellTagExist).
|
||||
*/
|
||||
static seriesSpellTagExists(userId: string, tagId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.isSpellTagExist(userId, tagId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a complete spell by ID for sync (array format).
|
||||
*/
|
||||
static fetchCompleteSpellById(spellId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT spell_id, series_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update FROM series_spells WHERE spell_id = ?';
|
||||
return db.all(query, [spellId]) as SeriesSpellsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer le sort complet.` : `Unable to retrieve complete spell.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a complete spell tag by ID for sync (array format).
|
||||
*/
|
||||
static fetchCompleteSpellTagById(tagId: string, lang: 'fr' | 'en' = 'fr'): SeriesSpellTagsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT tag_id, series_id, user_id, name, hashed_name, color, last_update FROM series_spell_tags WHERE tag_id = ?';
|
||||
return db.all(query, [tagId]) as SeriesSpellTagsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer le tag complet.` : `Unable to retrieve complete tag.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a series spell for sync (alias with compatible signature).
|
||||
*/
|
||||
static insertSyncSeriesSpell(spellId: string, seriesId: string, userId: string, name: string, nameHash: string, description: string, appearance: string, tags: string, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.insertSyncSpell(spellId, seriesId, userId, name, nameHash, description, appearance, tags, powerLevel, components, limitations, notes, lastUpdate, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a series spell for sync (simplified signature).
|
||||
*/
|
||||
static updateSyncSeriesSpell(spellId: string, userId: string, name: string, description: string, appearance: string, tags: string, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'UPDATE series_spells SET name = ?, description = ?, appearance = ?, tags = ?, power_level = ?, components = ?, limitations = ?, notes = ?, last_update = ? WHERE spell_id = ? AND user_id = ?';
|
||||
const params: SQLiteValue[] = [name, description, appearance, tags, powerLevel, components, limitations, notes, lastUpdate, spellId, userId];
|
||||
const updateResult: RunResult = db.run(query, params);
|
||||
return updateResult.changes > 0;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de mettre à jour le sort série pour sync.` : `Unable to update series spell for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a series spell tag for sync (alias with compatible signature).
|
||||
*/
|
||||
static insertSyncSeriesSpellTag(tagId: string, seriesId: string, userId: string, name: string, hashedName: string, color: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.insertSyncSpellTag(tagId, seriesId, userId, name, hashedName, color, lastUpdate, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a series spell tag for sync (simplified signature).
|
||||
*/
|
||||
static updateSyncSeriesSpellTag(tagId: string, userId: string, name: string, color: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'UPDATE series_spell_tags SET name = ?, color = ?, last_update = ? WHERE tag_id = ? AND user_id = ?';
|
||||
const params: SQLiteValue[] = [name, color, lastUpdate, tagId, userId];
|
||||
const updateResult: RunResult = db.run(query, params);
|
||||
return updateResult.changes > 0;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de mettre à jour le tag série pour sync.` : `Unable to update series tag for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,4 +429,127 @@ export default class SeriesWorldRepo {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static fetchWorldsTableForSync(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT world_id, series_id, user_id, name, hashed_name, history, politics, economy, religion, languages, last_update FROM series_worlds WHERE series_id = ?';
|
||||
return db.all(query, [seriesId]) as SeriesWorldsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les mondes pour sync.` : `Unable to retrieve worlds for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static fetchWorldElementsTableForSync(seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldElementsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT swe.element_id, swe.world_id, swe.user_id, swe.element_type, swe.name, swe.original_name, swe.description, swe.last_update FROM series_world_elements swe INNER JOIN series_worlds sw ON swe.world_id = sw.world_id WHERE sw.series_id = ?';
|
||||
return db.all(query, [seriesId]) as SeriesWorldElementsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de monde pour sync.` : `Unable to retrieve world elements for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all worlds for a series (alias for fetchSeriesWorldsTable).
|
||||
*/
|
||||
public static fetchSeriesWorlds(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldsTableResult[] {
|
||||
return this.fetchSeriesWorldsTable(userId, seriesId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all world elements for a series by series ID.
|
||||
*/
|
||||
public static fetchSeriesWorldElementsBySeriesId(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): SeriesWorldElementsTableResult[] {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'SELECT swe.element_id, swe.world_id, swe.user_id, swe.element_type, swe.name, swe.original_name, swe.description, swe.last_update FROM series_world_elements swe INNER JOIN series_worlds sw ON swe.world_id = sw.world_id WHERE sw.series_id = ? AND sw.user_id = ?';
|
||||
return db.all(query, [seriesId, userId]) as SeriesWorldElementsTableResult[];
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de récupérer les éléments de monde par série.` : `Unable to retrieve world elements by series.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a series world exists (alias for isWorldExist).
|
||||
*/
|
||||
public static seriesWorldExists(userId: string, worldId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.isWorldExist(userId, worldId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a series world element exists (alias for isWorldElementExist).
|
||||
*/
|
||||
public static seriesWorldElementExists(userId: string, elementId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.isWorldElementExist(userId, elementId, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a series world for sync (alias with compatible signature).
|
||||
*/
|
||||
public static insertSyncSeriesWorld(worldId: string, seriesId: string, userId: string, name: string, hashedName: string, history: string | null, politics: string | null, economy: string | null, religion: string | null, languages: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.insertSyncWorld(worldId, seriesId, userId, name, hashedName, history, politics, economy, religion, languages, lastUpdate, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a series world for sync (without hashedName).
|
||||
*/
|
||||
public static updateSyncSeriesWorld(worldId: string, userId: string, name: string, history: string | null, politics: string | null, economy: string | null, religion: string | null, languages: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'UPDATE series_worlds SET name = ?, history = ?, politics = ?, economy = ?, religion = ?, languages = ?, last_update = ? WHERE world_id = ? AND user_id = ?';
|
||||
const params: SQLiteValue[] = [name, history, politics, economy, religion, languages, lastUpdate, worldId, userId];
|
||||
const updateResult: RunResult = db.run(query, params);
|
||||
return updateResult.changes > 0;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de mettre à jour le monde série pour sync.` : `Unable to update series world for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a series world element for sync (alias with compatible signature).
|
||||
*/
|
||||
public static insertSyncSeriesWorldElement(elementId: string, worldId: string, userId: string, elementType: number, name: string, originalName: string, description: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.insertSyncWorldElement(elementId, worldId, userId, elementType, name, originalName, description, lastUpdate, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a series world element for sync (without elementType and originalName).
|
||||
*/
|
||||
public static updateSyncSeriesWorldElement(elementId: string, userId: string, name: string, description: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'UPDATE series_world_elements SET name = ?, description = ?, last_update = ? WHERE element_id = ? AND user_id = ?';
|
||||
const params: SQLiteValue[] = [name, description, lastUpdate, elementId, userId];
|
||||
const updateResult: RunResult = db.run(query, params);
|
||||
return updateResult.changes > 0;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`DB Error: ${error.message}`);
|
||||
throw new Error(lang === 'fr' ? `Impossible de mettre à jour l'élément de monde série pour sync.` : `Unable to update series world element for sync.`);
|
||||
} else {
|
||||
throw new Error(lang === 'fr' ? "Une erreur inconnue s'est produite." : "An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,4 +539,15 @@ export default class SeriesRepo {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a series exists for a user (alias for isSeriesExist).
|
||||
* @param userId - The unique identifier of the user
|
||||
* @param seriesId - The unique identifier of the series
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the series exists
|
||||
*/
|
||||
public static seriesExists(userId: string, seriesId: string, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
return this.isSeriesExist(userId, seriesId, lang);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ export interface SpellResult extends Record<string, SQLiteValue> {
|
||||
spell_id: string;
|
||||
book_id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
appearance: string;
|
||||
tags: string;
|
||||
description: string | null;
|
||||
appearance: string | null;
|
||||
tags: string | null;
|
||||
power_level: string | null;
|
||||
components: string | null;
|
||||
limitations: string | null;
|
||||
@@ -21,9 +21,9 @@ export interface BookSpellsTable extends Record<string, SQLiteValue> {
|
||||
user_id: string;
|
||||
name: string;
|
||||
name_hash: string;
|
||||
description: string;
|
||||
appearance: string;
|
||||
tags: string;
|
||||
description: string | null;
|
||||
appearance: string | null;
|
||||
tags: string | null;
|
||||
power_level: string | null;
|
||||
components: string | null;
|
||||
limitations: string | null;
|
||||
@@ -301,7 +301,7 @@ export default class SpellRepo {
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the insertion was successful
|
||||
*/
|
||||
static insertSyncSpell(spellId: string, bookId: string, userId: string, name: string, nameHash: string, description: string, appearance: string, tags: string, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
static insertSyncSpell(spellId: string, bookId: string, userId: string, name: string, nameHash: string, description: string | null, appearance: string | null, tags: string | null, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'INSERT OR REPLACE INTO book_spells (spell_id, book_id, user_id, name, name_hash, description, appearance, tags, power_level, components, limitations, notes, last_update) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)';
|
||||
@@ -359,7 +359,7 @@ export default class SpellRepo {
|
||||
* @param lang - The language for error messages ('fr' or 'en')
|
||||
* @returns True if the update was successful
|
||||
*/
|
||||
static updateSyncSpell(userId: string, spellId: string, name: string, nameHash: string, description: string, appearance: string, tags: string, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
static updateSyncSpell(userId: string, spellId: string, name: string, nameHash: string, description: string | null, appearance: string | null, tags: string | null, powerLevel: string | null, components: string | null, limitations: string | null, notes: string | null, lastUpdate: number, lang: 'fr' | 'en' = 'fr'): boolean {
|
||||
try {
|
||||
const db: Database = System.getDb();
|
||||
const query: string = 'UPDATE book_spells SET name=?, name_hash=?, description=?, appearance=?, tags=?, power_level=?, components=?, limitations=?, notes=?, last_update=? WHERE spell_id=? AND user_id=?';
|
||||
|
||||
@@ -19,7 +19,205 @@ const schemaVersion = 3;
|
||||
* DEV ONLY - S'exécute à chaque refresh, pas besoin de version
|
||||
* Mets ta query, test, efface après
|
||||
*/
|
||||
const devQueries: string[] = [];
|
||||
const devQueries: string[] = [
|
||||
// V3 Migration: Series tables and series_*_id columns
|
||||
|
||||
// Book Series
|
||||
`CREATE TABLE IF NOT EXISTS book_series (
|
||||
series_id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
hashed_name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
cover_image TEXT,
|
||||
last_update INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (user_id) REFERENCES erit_users(user_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_book_series_user ON book_series(user_id)`,
|
||||
|
||||
// Series Books
|
||||
`CREATE TABLE IF NOT EXISTS series_books (
|
||||
series_id TEXT NOT NULL,
|
||||
book_id TEXT NOT NULL,
|
||||
book_order INTEGER NOT NULL DEFAULT 1,
|
||||
last_update INTEGER DEFAULT 0,
|
||||
PRIMARY KEY (series_id, book_id),
|
||||
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (book_id) REFERENCES erit_books(book_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_books_book ON series_books(book_id)`,
|
||||
|
||||
// Series Characters
|
||||
`CREATE TABLE IF NOT EXISTS series_characters (
|
||||
character_id TEXT PRIMARY KEY,
|
||||
series_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT,
|
||||
nickname TEXT,
|
||||
age TEXT,
|
||||
gender TEXT,
|
||||
species TEXT,
|
||||
nationality TEXT,
|
||||
status TEXT,
|
||||
category TEXT NOT NULL,
|
||||
title TEXT,
|
||||
image TEXT,
|
||||
role TEXT,
|
||||
biography TEXT,
|
||||
history TEXT,
|
||||
speech_pattern TEXT,
|
||||
catchphrase TEXT,
|
||||
residence TEXT,
|
||||
notes TEXT,
|
||||
color TEXT,
|
||||
last_update INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_characters_series ON series_characters(series_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_characters_user ON series_characters(user_id)`,
|
||||
|
||||
// Series Characters Attributes
|
||||
`CREATE TABLE IF NOT EXISTS series_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 series_characters(character_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_char_attrs_character ON series_characters_attributes(character_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_char_attrs_user ON series_characters_attributes(user_id)`,
|
||||
|
||||
// Series Worlds
|
||||
`CREATE TABLE IF NOT EXISTS series_worlds (
|
||||
world_id TEXT PRIMARY KEY,
|
||||
series_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
hashed_name TEXT NOT NULL,
|
||||
history TEXT,
|
||||
politics TEXT,
|
||||
economy TEXT,
|
||||
religion TEXT,
|
||||
languages TEXT,
|
||||
last_update INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_worlds_series ON series_worlds(series_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_worlds_user ON series_worlds(user_id)`,
|
||||
|
||||
// Series World Elements
|
||||
`CREATE TABLE IF NOT EXISTS series_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 series_worlds(world_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_world_elements_world ON series_world_elements(world_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_world_elements_user ON series_world_elements(user_id)`,
|
||||
|
||||
// Series Locations
|
||||
`CREATE TABLE IF NOT EXISTS series_locations (
|
||||
loc_id TEXT PRIMARY KEY,
|
||||
series_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 (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_locations_series ON series_locations(series_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_locations_user ON series_locations(user_id)`,
|
||||
|
||||
// Series Location Elements
|
||||
`CREATE TABLE IF NOT EXISTS series_location_elements (
|
||||
element_id TEXT PRIMARY KEY,
|
||||
location_id 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_id) REFERENCES series_locations(loc_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_loc_elements_location ON series_location_elements(location_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_loc_elements_user ON series_location_elements(user_id)`,
|
||||
|
||||
// Series Location Sub Elements
|
||||
`CREATE TABLE IF NOT EXISTS series_location_sub_elements (
|
||||
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 series_location_elements(element_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_element ON series_location_sub_elements(element_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_user ON series_location_sub_elements(user_id)`,
|
||||
|
||||
// Series Spells
|
||||
`CREATE TABLE IF NOT EXISTS series_spells (
|
||||
spell_id TEXT PRIMARY KEY,
|
||||
series_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
name_hash TEXT NOT NULL,
|
||||
description TEXT,
|
||||
appearance TEXT,
|
||||
tags TEXT,
|
||||
power_level TEXT,
|
||||
components TEXT,
|
||||
limitations TEXT,
|
||||
notes TEXT,
|
||||
last_update INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_spells_series ON series_spells(series_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_spells_user ON series_spells(user_id)`,
|
||||
|
||||
// Series Spell Tags
|
||||
`CREATE TABLE IF NOT EXISTS series_spell_tags (
|
||||
tag_id TEXT PRIMARY KEY,
|
||||
series_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
hashed_name TEXT NOT NULL,
|
||||
color TEXT,
|
||||
last_update INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_spell_tags_series ON series_spell_tags(series_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_series_spell_tags_user ON series_spell_tags(user_id)`,
|
||||
|
||||
// Add series_*_id columns to existing book tables (will fail silently if already exists)
|
||||
`ALTER TABLE book_characters ADD COLUMN series_character_id TEXT DEFAULT NULL`,
|
||||
`ALTER TABLE book_world ADD COLUMN series_world_id TEXT DEFAULT NULL`,
|
||||
`ALTER TABLE book_location ADD COLUMN series_location_id TEXT DEFAULT NULL`,
|
||||
`ALTER TABLE book_spells ADD COLUMN series_spell_id TEXT DEFAULT NULL`,
|
||||
|
||||
// Removed Items (sync deletion tracking)
|
||||
`CREATE TABLE IF NOT EXISTS removed_items (
|
||||
removal_id TEXT PRIMARY KEY,
|
||||
table_name TEXT NOT NULL,
|
||||
entity_id TEXT NOT NULL,
|
||||
book_id TEXT,
|
||||
user_id TEXT NOT NULL,
|
||||
deleted_at INTEGER NOT NULL
|
||||
)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_removed_items_user ON removed_items(user_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_removed_items_book ON removed_items(book_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_removed_items_deleted_at ON removed_items(deleted_at)`,
|
||||
`CREATE UNIQUE INDEX IF NOT EXISTS idx_removed_items_entity ON removed_items(table_name, entity_id)`,
|
||||
];
|
||||
|
||||
const isDev:boolean = !app.isPackaged;
|
||||
|
||||
@@ -123,9 +321,9 @@ function migrateFromOldSystem(db: Database): void {
|
||||
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,
|
||||
description TEXT,
|
||||
appearance TEXT,
|
||||
tags TEXT,
|
||||
power_level TEXT,
|
||||
components TEXT,
|
||||
limitations TEXT,
|
||||
@@ -172,7 +370,7 @@ function migrateFromOldSystem(db: Database): void {
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_element ON series_location_sub_elements(element_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_user ON series_location_sub_elements(user_id)`);
|
||||
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS series_spells (spell_id TEXT PRIMARY KEY, series_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, power_level TEXT, components TEXT, limitations TEXT, notes TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS series_spells (spell_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT NOT NULL, name_hash TEXT NOT NULL, description TEXT, appearance TEXT, tags TEXT, power_level TEXT, components TEXT, limitations TEXT, notes TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_series ON series_spells(series_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_user ON series_spells(user_id)`);
|
||||
|
||||
@@ -186,6 +384,23 @@ function migrateFromOldSystem(db: Database): void {
|
||||
addColumn(db, 'book_location', 'series_location_id', 'TEXT DEFAULT NULL');
|
||||
addColumn(db, 'book_spells', 'series_spell_id', 'TEXT DEFAULT NULL');
|
||||
|
||||
// Removed Items (sync deletion tracking)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS removed_items (
|
||||
removal_id TEXT PRIMARY KEY,
|
||||
table_name TEXT NOT NULL,
|
||||
entity_id TEXT NOT NULL,
|
||||
book_id TEXT,
|
||||
user_id TEXT NOT NULL,
|
||||
deleted_at INTEGER NOT NULL,
|
||||
removed_time INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_removed_items_user ON removed_items(user_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_removed_items_book ON removed_items(book_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_removed_items_deleted_at ON removed_items(deleted_at)`);
|
||||
db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_removed_items_entity ON removed_items(table_name, entity_id)`);
|
||||
|
||||
// Drop old schema version table
|
||||
db.exec('DROP TABLE IF EXISTS _schema_version');
|
||||
}
|
||||
@@ -255,9 +470,9 @@ export function runMigrations(db: Database): void {
|
||||
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,
|
||||
description TEXT,
|
||||
appearance TEXT,
|
||||
tags TEXT,
|
||||
power_level TEXT,
|
||||
components TEXT,
|
||||
limitations TEXT,
|
||||
@@ -333,7 +548,7 @@ export function runMigrations(db: Database): void {
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_loc_sub_elements_user ON series_location_sub_elements(user_id)`);
|
||||
|
||||
// Series Spells
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS series_spells (spell_id TEXT PRIMARY KEY, series_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, power_level TEXT, components TEXT, limitations TEXT, notes TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||
db.exec(`CREATE TABLE IF NOT EXISTS series_spells (spell_id TEXT PRIMARY KEY, series_id TEXT NOT NULL, user_id TEXT NOT NULL, name TEXT NOT NULL, name_hash TEXT NOT NULL, description TEXT, appearance TEXT, tags TEXT, power_level TEXT, components TEXT, limitations TEXT, notes TEXT, last_update INTEGER DEFAULT 0, FOREIGN KEY (series_id) REFERENCES book_series(series_id) ON DELETE CASCADE);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_series ON series_spells(series_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_user ON series_spells(user_id)`);
|
||||
|
||||
@@ -347,6 +562,23 @@ export function runMigrations(db: Database): void {
|
||||
addColumn(db, 'book_world', 'series_world_id', 'TEXT DEFAULT NULL');
|
||||
addColumn(db, 'book_location', 'series_location_id', 'TEXT DEFAULT NULL');
|
||||
addColumn(db, 'book_spells', 'series_spell_id', 'TEXT DEFAULT NULL');
|
||||
|
||||
// Removed Items (sync deletion tracking)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS removed_items (
|
||||
removal_id TEXT PRIMARY KEY,
|
||||
table_name TEXT NOT NULL,
|
||||
entity_id TEXT NOT NULL,
|
||||
book_id TEXT,
|
||||
user_id TEXT NOT NULL,
|
||||
deleted_at INTEGER NOT NULL,
|
||||
removed_time INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_removed_items_user ON removed_items(user_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_removed_items_book ON removed_items(book_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_removed_items_deleted_at ON removed_items(deleted_at)`);
|
||||
db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_removed_items_entity ON removed_items(table_name, entity_id)`);
|
||||
}
|
||||
|
||||
setDbVersion(db, schemaVersion);
|
||||
@@ -808,9 +1040,9 @@ export function initializeSchema(db: Database): void {
|
||||
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,
|
||||
description TEXT,
|
||||
appearance TEXT,
|
||||
tags TEXT,
|
||||
power_level TEXT,
|
||||
components TEXT,
|
||||
limitations TEXT,
|
||||
@@ -978,8 +1210,8 @@ export function initializeSchema(db: Database): void {
|
||||
user_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
name_hash TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
appearance TEXT NOT NULL,
|
||||
description TEXT,
|
||||
appearance TEXT,
|
||||
tags TEXT,
|
||||
power_level TEXT,
|
||||
components TEXT,
|
||||
@@ -1004,6 +1236,19 @@ export function initializeSchema(db: Database): void {
|
||||
);
|
||||
`);
|
||||
|
||||
// Removed Items (sync deletion tracking)
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS removed_items (
|
||||
removal_id TEXT PRIMARY KEY,
|
||||
table_name TEXT NOT NULL,
|
||||
entity_id TEXT NOT NULL,
|
||||
book_id TEXT,
|
||||
user_id TEXT NOT NULL,
|
||||
deleted_at INTEGER NOT NULL,
|
||||
removed_time INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
`);
|
||||
|
||||
// Create indexes for better performance
|
||||
createIndexes(db);
|
||||
}
|
||||
@@ -1049,6 +1294,11 @@ function createIndexes(db: Database): void {
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spells_user ON series_spells(user_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spell_tags_series ON series_spell_tags(series_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_series_spell_tags_user ON series_spell_tags(user_id)`);
|
||||
// Removed items indexes
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_removed_items_user ON removed_items(user_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_removed_items_book ON removed_items(book_id)`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_removed_items_deleted_at ON removed_items(deleted_at)`);
|
||||
db.exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_removed_items_entity ON removed_items(table_name, entity_id)`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -270,11 +270,12 @@ ipcMain.handle(
|
||||
interface RemoveIncidentData {
|
||||
bookId: string;
|
||||
incidentId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
ipcMain.handle('db:book:incident:remove', createHandler<RemoveIncidentData, boolean>(
|
||||
function(userId: string, data: RemoveIncidentData, lang: 'fr' | 'en') {
|
||||
return Incident.removeIncident(userId, data.bookId, data.incidentId, lang);
|
||||
return Incident.removeIncident(userId, data.bookId, data.incidentId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -297,12 +298,14 @@ ipcMain.handle('db:book:plot:add', createHandler<AddPlotPointData, string>(
|
||||
// DELETE /book/plot/remove - Remove plot point
|
||||
interface RemovePlotData {
|
||||
plotId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle(
|
||||
'db:book:plot:remove',
|
||||
createHandler<RemovePlotData, boolean>(
|
||||
function(userId: string, data: RemovePlotData, lang: 'fr' | 'en') {
|
||||
return PlotPoint.removePlotPoint(userId, data.plotId, lang);
|
||||
return PlotPoint.removePlotPoint(userId, data.bookId, data.plotId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -319,10 +322,11 @@ ipcMain.handle('db:book:issue:add', createHandler<AddIssueData, string>(
|
||||
interface RemoveIssueData {
|
||||
bookId: string;
|
||||
issueId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle('db:book:issue:remove', createHandler<RemoveIssueData, boolean>(
|
||||
function(userId: string, data: RemoveIssueData, lang: 'fr' | 'en') {
|
||||
return Issue.removeIssue(userId, data.issueId, lang);
|
||||
return Issue.removeIssue(userId, data.bookId, data.issueId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -363,10 +367,12 @@ ipcMain.handle('db:book:world:element:add', createHandler<AddWorldElementData, s
|
||||
// DELETE /book/world/element/delete - Remove element from world
|
||||
interface RemoveWorldElementData {
|
||||
elementId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle('db:book:world:element:remove', createHandler<RemoveWorldElementData, boolean>(
|
||||
function(userId: string, data: RemoveWorldElementData, lang: 'fr' | 'en') {
|
||||
return World.removeElementFromWorld(userId, data.elementId, lang);
|
||||
return World.removeElementFromWorld(userId, data.bookId, data.elementId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -374,10 +380,11 @@ ipcMain.handle('db:book:world:element:remove', createHandler<RemoveWorldElementD
|
||||
// DELETE /book/delete - Delete book
|
||||
interface DeleteBookData {
|
||||
id: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle('db:book:delete', createHandler<DeleteBookData, boolean>(
|
||||
function(userId: string, data: DeleteBookData, lang: 'fr' | 'en') {
|
||||
return Book.removeBook(userId, data.id, lang);
|
||||
return Book.removeBook(userId, data.id, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -120,11 +120,12 @@ ipcMain.handle('db:chapter:add', createHandler<AddChapterData, string>(
|
||||
// DELETE /chapter/remove - Remove chapter
|
||||
interface RemoveChapterData {
|
||||
chapterId: string;
|
||||
bookId?: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle('db:chapter:remove', createHandler<RemoveChapterData, boolean>(
|
||||
function(userId: string, data: RemoveChapterData, lang: 'fr' | 'en'): boolean {
|
||||
return Chapter.removeChapter(userId, data.chapterId, lang);
|
||||
return Chapter.removeChapter(userId, data.bookId, data.chapterId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -157,10 +158,12 @@ ipcMain.handle('db:chapter:information:add', createHandler<AddChapterInformation
|
||||
// DELETE /chapter/resume/remove - Remove chapter information
|
||||
interface RemoveChapterInfoData {
|
||||
chapterInfoId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle('db:chapter:information:remove', createHandler<RemoveChapterInfoData, boolean>(
|
||||
function(userId: string, data: RemoveChapterInfoData, lang: 'fr' | 'en'): boolean {
|
||||
return Chapter.removeChapterInformation(userId, data.chapterInfoId, lang);
|
||||
return Chapter.removeChapterInformation(userId, data.bookId, data.chapterInfoId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -57,10 +57,12 @@ ipcMain.handle('db:character:attribute:add', createHandler<AddAttributeData, str
|
||||
// DELETE /character/attribute/delete - Delete character attribute
|
||||
interface DeleteAttributeData {
|
||||
attributeId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle('db:character:attribute:delete', createHandler<DeleteAttributeData, boolean>(
|
||||
function(userId: string, data: DeleteAttributeData, lang: 'fr' | 'en'): boolean {
|
||||
return Character.deleteAttribute(userId, data.attributeId, lang);
|
||||
return Character.deleteAttribute(userId, data.bookId, data.attributeId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -79,10 +81,12 @@ ipcMain.handle('db:character:update', createHandler<UpdateCharacterData, boolean
|
||||
// DELETE /character/delete - Delete character
|
||||
interface DeleteCharacterData {
|
||||
characterId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle('db:character:delete', createHandler<DeleteCharacterData, boolean>(
|
||||
function(userId: string, data: DeleteCharacterData, lang: 'fr' | 'en'): boolean {
|
||||
return Character.deleteCharacter(userId, data.characterId, lang);
|
||||
return Character.deleteCharacter(userId, data.bookId, data.characterId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -90,10 +90,12 @@ ipcMain.handle('db:location:section:update', createHandler<UpdateSectionWithSeri
|
||||
// DELETE /location/delete - Delete location section
|
||||
interface DeleteLocationData {
|
||||
locationId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle('db:location:delete', createHandler<DeleteLocationData, boolean>(
|
||||
function(userId: string, data: DeleteLocationData, lang: 'fr' | 'en'): boolean {
|
||||
return Location.deleteLocationSection(userId, data.locationId, lang);
|
||||
return Location.deleteLocationSection(userId, data.bookId, data.locationId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -101,10 +103,12 @@ ipcMain.handle('db:location:delete', createHandler<DeleteLocationData, boolean>(
|
||||
// DELETE /location/element/delete - Delete location element
|
||||
interface DeleteLocationElementData {
|
||||
elementId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle('db:location:element:delete', createHandler<DeleteLocationElementData, boolean>(
|
||||
function(userId: string, data: DeleteLocationElementData, lang: 'fr' | 'en'): boolean {
|
||||
return Location.deleteLocationElement(userId, data.elementId, lang);
|
||||
return Location.deleteLocationElement(userId, data.bookId, data.elementId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -112,10 +116,12 @@ ipcMain.handle('db:location:element:delete', createHandler<DeleteLocationElement
|
||||
// DELETE /location/sub-element/delete - Delete location sub-element
|
||||
interface DeleteLocationSubElementData {
|
||||
subElementId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
ipcMain.handle('db:location:subelement:delete', createHandler<DeleteLocationSubElementData, boolean>(
|
||||
function(userId: string, data: DeleteLocationSubElementData, lang: 'fr' | 'en'): boolean {
|
||||
return Location.deleteLocationSubElement(userId, data.subElementId, lang);
|
||||
return Location.deleteLocationSubElement(userId, data.bookId, data.subElementId, data.deletedAt, lang);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -21,6 +21,7 @@ interface UpdateCharacterData {
|
||||
|
||||
interface DeleteCharacterData {
|
||||
characterId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
interface AddAttributeData {
|
||||
@@ -31,6 +32,7 @@ interface AddAttributeData {
|
||||
|
||||
interface DeleteAttributeData {
|
||||
attributeId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
// GET /series/character/list - Get character list
|
||||
@@ -64,7 +66,7 @@ ipcMain.handle('db:series:character:update', createHandler<UpdateCharacterData,
|
||||
// DELETE /series/character/delete - Delete character
|
||||
ipcMain.handle('db:series:character:delete', createHandler<DeleteCharacterData, boolean>(
|
||||
function(userId: string, data: DeleteCharacterData, lang: 'fr' | 'en'): boolean {
|
||||
return SeriesCharacter.deleteCharacter(userId, data.characterId, lang);
|
||||
return SeriesCharacter.deleteCharacter(userId, data.characterId, data.deletedAt, lang);
|
||||
}
|
||||
));
|
||||
|
||||
@@ -78,6 +80,6 @@ ipcMain.handle('db:series:character:attribute:add', createHandler<AddAttributeDa
|
||||
// DELETE /series/character/attribute/delete - Delete attribute
|
||||
ipcMain.handle('db:series:character:attribute:delete', createHandler<DeleteAttributeData, boolean>(
|
||||
function(userId: string, data: DeleteAttributeData, lang: 'fr' | 'en'): boolean {
|
||||
return SeriesCharacter.deleteAttribute(userId, data.attributeId, lang);
|
||||
return SeriesCharacter.deleteAttribute(userId, data.attributeId, data.deletedAt, lang);
|
||||
}
|
||||
));
|
||||
|
||||
@@ -25,14 +25,17 @@ interface AddSubElementData {
|
||||
|
||||
interface DeleteLocationData {
|
||||
locationId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
interface DeleteElementData {
|
||||
elementId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
interface DeleteSubElementData {
|
||||
subElementId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
// GET /series/location/list - Get location list
|
||||
@@ -66,20 +69,20 @@ ipcMain.handle('db:series:location:subelement:add', createHandler<AddSubElementD
|
||||
// DELETE /series/location/delete - Delete location
|
||||
ipcMain.handle('db:series:location:delete', createHandler<DeleteLocationData, boolean>(
|
||||
function(userId: string, data: DeleteLocationData, lang: 'fr' | 'en'): boolean {
|
||||
return SeriesLocation.deleteLocation(userId, data.locationId, lang);
|
||||
return SeriesLocation.deleteLocation(userId, data.locationId, data.deletedAt, lang);
|
||||
}
|
||||
));
|
||||
|
||||
// DELETE /series/location/element/delete - Delete element
|
||||
ipcMain.handle('db:series:location:element:delete', createHandler<DeleteElementData, boolean>(
|
||||
function(userId: string, data: DeleteElementData, lang: 'fr' | 'en'): boolean {
|
||||
return SeriesLocation.deleteElement(userId, data.elementId, lang);
|
||||
return SeriesLocation.deleteElement(userId, data.elementId, data.deletedAt, lang);
|
||||
}
|
||||
));
|
||||
|
||||
// DELETE /series/location/sub-element/delete - Delete sub-element
|
||||
ipcMain.handle('db:series:location:subelement:delete', createHandler<DeleteSubElementData, boolean>(
|
||||
function(userId: string, data: DeleteSubElementData, lang: 'fr' | 'en'): boolean {
|
||||
return SeriesLocation.deleteSubElement(userId, data.subElementId, lang);
|
||||
return SeriesLocation.deleteSubElement(userId, data.subElementId, data.deletedAt, lang);
|
||||
}
|
||||
));
|
||||
|
||||
@@ -36,6 +36,7 @@ interface UpdateSpellData {
|
||||
|
||||
interface DeleteSpellData {
|
||||
spellId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
interface AddTagData {
|
||||
@@ -52,6 +53,7 @@ interface UpdateTagData {
|
||||
|
||||
interface DeleteTagData {
|
||||
tagId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
// GET /series/spell/list - Get spell list
|
||||
@@ -85,7 +87,7 @@ ipcMain.handle('db:series:spell:update', createHandler<UpdateSpellData, boolean>
|
||||
// DELETE /series/spell/delete - Delete spell
|
||||
ipcMain.handle('db:series:spell:delete', createHandler<DeleteSpellData, boolean>(
|
||||
function(userId: string, data: DeleteSpellData, lang: 'fr' | 'en'): boolean {
|
||||
return SeriesSpell.deleteSpell(userId, data.spellId, lang);
|
||||
return SeriesSpell.deleteSpell(userId, data.spellId, data.deletedAt, lang);
|
||||
}
|
||||
));
|
||||
|
||||
@@ -106,6 +108,6 @@ ipcMain.handle('db:series:spell:tag:update', createHandler<UpdateTagData, boolea
|
||||
// DELETE /series/spell/tag/delete - Delete tag
|
||||
ipcMain.handle('db:series:spell:tag:delete', createHandler<DeleteTagData, boolean>(
|
||||
function(userId: string, data: DeleteTagData, lang: 'fr' | 'en'): boolean {
|
||||
return SeriesSpell.deleteTag(userId, data.tagId, lang);
|
||||
return SeriesSpell.deleteTag(userId, data.tagId, data.deletedAt, lang);
|
||||
}
|
||||
));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { createHandler } from '../database/LocalSystem.js';
|
||||
import SeriesSync, { SeriesSyncUploadPayload, SeriesSyncResult } from '../database/models/SeriesSync.js';
|
||||
import SeriesSync, { SeriesSyncUploadPayload, SeriesSyncResult, CompleteSeries, SyncedSeries } from '../database/models/SeriesSync.js';
|
||||
import { SyncElementType } from '../database/repositories/series-sync.repo.js';
|
||||
|
||||
interface UploadToSeriesData {
|
||||
@@ -10,7 +10,6 @@ interface UploadToSeriesData {
|
||||
value: string;
|
||||
}
|
||||
|
||||
// POST /series/sync/upload - Upload field to series
|
||||
ipcMain.handle('db:series:sync:upload', createHandler<UploadToSeriesData, SeriesSyncResult>(
|
||||
function(userId: string, data: UploadToSeriesData, lang: 'fr' | 'en'): SeriesSyncResult {
|
||||
const payload: SeriesSyncUploadPayload = {
|
||||
@@ -22,3 +21,34 @@ ipcMain.handle('db:series:sync:upload', createHandler<UploadToSeriesData, Series
|
||||
return SeriesSync.uploadFieldToSeries(userId, payload, lang);
|
||||
}
|
||||
));
|
||||
|
||||
ipcMain.handle('db:series:synced', createHandler<void, SyncedSeries[]>(
|
||||
function(userId: string, _data: void, lang: 'fr' | 'en'): SyncedSeries[] {
|
||||
return SeriesSync.getSyncedSeries(userId, lang);
|
||||
}
|
||||
));
|
||||
|
||||
ipcMain.handle('db:series:uploadToServer', createHandler<string, CompleteSeries>(
|
||||
async function(userId: string, seriesId: string, lang: 'fr' | 'en'): Promise<CompleteSeries> {
|
||||
return SeriesSync.getCompleteSeriesForUpload(userId, seriesId, lang);
|
||||
}
|
||||
));
|
||||
|
||||
ipcMain.handle('db:series:syncSave', createHandler<CompleteSeries, boolean>(
|
||||
async function(userId: string, completeSeries: CompleteSeries, lang: 'fr' | 'en'): Promise<boolean> {
|
||||
return SeriesSync.saveCompleteSeries(userId, completeSeries, lang);
|
||||
}
|
||||
));
|
||||
|
||||
ipcMain.handle('db:series:sync:toClient', createHandler<CompleteSeries, boolean>(
|
||||
async function(userId: string, completeSeries: CompleteSeries, lang: 'fr' | 'en'): Promise<boolean> {
|
||||
return SeriesSync.syncSeriesFromServerToClient(userId, completeSeries, lang);
|
||||
}
|
||||
));
|
||||
|
||||
ipcMain.handle('db:series:sync:toServer', createHandler<object, CompleteSeries>(
|
||||
async function(userId: string, syncCompare: object, lang: 'fr' | 'en'): Promise<CompleteSeries> {
|
||||
const seriesId = (syncCompare as { id: string }).id;
|
||||
return SeriesSync.getCompleteSeriesForUpload(userId, seriesId, lang);
|
||||
}
|
||||
));
|
||||
|
||||
@@ -30,6 +30,7 @@ interface AddElementData {
|
||||
|
||||
interface DeleteElementData {
|
||||
elementId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
// GET /series/world/list - Get world list
|
||||
@@ -71,6 +72,6 @@ ipcMain.handle('db:series:world:element:add', createHandler<AddElementData, stri
|
||||
// DELETE /series/world/element/delete - Delete element
|
||||
ipcMain.handle('db:series:world:element:delete', createHandler<DeleteElementData, boolean>(
|
||||
function(userId: string, data: DeleteElementData, lang: 'fr' | 'en'): boolean {
|
||||
return SeriesWorld.deleteElement(userId, data.elementId, lang);
|
||||
return SeriesWorld.deleteElement(userId, data.elementId, data.deletedAt, lang);
|
||||
}
|
||||
));
|
||||
|
||||
@@ -16,6 +16,7 @@ interface UpdateSeriesData {
|
||||
|
||||
interface DeleteSeriesData {
|
||||
seriesId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
interface GetSeriesDetailData {
|
||||
@@ -31,6 +32,7 @@ interface AddBookToSeriesData {
|
||||
interface RemoveBookFromSeriesData {
|
||||
seriesId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
interface UpdateBooksOrderData {
|
||||
@@ -77,7 +79,7 @@ ipcMain.handle('db:series:update', createHandler<UpdateSeriesData, boolean>(
|
||||
// DELETE /series/delete - Delete series
|
||||
ipcMain.handle('db:series:delete', createHandler<DeleteSeriesData, boolean>(
|
||||
async function(userId: string, data: DeleteSeriesData, lang: 'fr' | 'en'): Promise<boolean> {
|
||||
return await Series.deleteSeries(userId, data.seriesId, lang);
|
||||
return await Series.deleteSeries(userId, data.seriesId, data.deletedAt, lang);
|
||||
}
|
||||
));
|
||||
|
||||
@@ -98,7 +100,7 @@ ipcMain.handle('db:series:book:add', createHandler<AddBookToSeriesData, boolean>
|
||||
// DELETE /series/book/remove - Remove book from series
|
||||
ipcMain.handle('db:series:book:remove', createHandler<RemoveBookFromSeriesData, boolean>(
|
||||
async function(userId: string, data: RemoveBookFromSeriesData, lang: 'fr' | 'en'): Promise<boolean> {
|
||||
return await Series.removeBookFromSeries(userId, data.seriesId, data.bookId, lang);
|
||||
return await Series.removeBookFromSeries(userId, data.seriesId, data.bookId, data.deletedAt, lang);
|
||||
}
|
||||
));
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ interface UpdateSpellData {
|
||||
|
||||
interface DeleteSpellData {
|
||||
spellId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
interface CreateTagData {
|
||||
@@ -63,6 +65,7 @@ interface UpdateTagData {
|
||||
interface DeleteTagData {
|
||||
tagId: string;
|
||||
bookId: string;
|
||||
deletedAt: number;
|
||||
}
|
||||
|
||||
// ==================== SPELL HANDLERS ====================
|
||||
@@ -152,7 +155,7 @@ ipcMain.handle(
|
||||
'db:spell:delete',
|
||||
createHandler<DeleteSpellData, boolean>(
|
||||
function (userId: string, data: DeleteSpellData, lang: 'fr' | 'en'): boolean {
|
||||
return Spell.deleteSpell(userId, data.spellId, lang);
|
||||
return Spell.deleteSpell(userId, data.bookId, data.spellId, data.deletedAt, lang);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -198,7 +201,7 @@ ipcMain.handle(
|
||||
'db:spell:tag:delete',
|
||||
createHandler<DeleteTagData, boolean>(
|
||||
function (userId: string, data: DeleteTagData, lang: 'fr' | 'en'): boolean {
|
||||
return Spell.deleteSpellTag(userId, data.tagId, data.bookId, lang);
|
||||
return Spell.deleteSpellTag(userId, data.bookId, data.tagId, data.deletedAt, lang);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
122
electron/ipc/tombstone.ipc.ts
Normal file
122
electron/ipc/tombstone.ipc.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { createHandler } from '../database/LocalSystem.js';
|
||||
import RemovedItemsRepository, { RemovedItemRecord } from '../database/repositories/removed-items.repository.js';
|
||||
import Book from '../database/models/Book.js';
|
||||
import Chapter from '../database/models/Chapter.js';
|
||||
import Character from '../database/models/Character.js';
|
||||
import Location from '../database/models/Location.js';
|
||||
import World from '../database/models/World.js';
|
||||
import Incident from '../database/models/Incident.js';
|
||||
import PlotPoint from '../database/models/PlotPoint.js';
|
||||
import Issue from '../database/models/Issue.js';
|
||||
import Spell from '../database/models/Spell.js';
|
||||
import Series from '../database/models/Series.js';
|
||||
import SeriesCharacter from '../database/models/SeriesCharacter.js';
|
||||
import SeriesLocation from '../database/models/SeriesLocation.js';
|
||||
import SeriesWorld from '../database/models/SeriesWorld.js';
|
||||
import SeriesSpell from '../database/models/SeriesSpell.js';
|
||||
|
||||
/**
|
||||
* Get tombstones since a specific timestamp.
|
||||
*/
|
||||
ipcMain.handle('db:tombstones:since', createHandler<number, RemovedItemRecord[]>(
|
||||
function(userId: string, since: number, lang: 'fr' | 'en'): RemovedItemRecord[] {
|
||||
return RemovedItemsRepository.getDeletionsSince(userId, since, lang);
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Apply server tombstones for book entities locally.
|
||||
*/
|
||||
ipcMain.handle('db:tombstones:apply:books', createHandler<RemovedItemRecord[], void>(
|
||||
function(userId: string, tombstones: RemovedItemRecord[], lang: 'fr' | 'en'): void {
|
||||
for (const tombstone of tombstones) {
|
||||
switch (tombstone.table_name) {
|
||||
case 'erit_books':
|
||||
Book.removeBook(userId, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_chapters':
|
||||
Chapter.removeChapter(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_chapter_infos':
|
||||
Chapter.removeChapterInformation(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_characters':
|
||||
Character.deleteCharacter(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_characters_attributes':
|
||||
Character.deleteAttribute(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_location':
|
||||
Location.deleteLocationSection(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'location_element':
|
||||
Location.deleteLocationElement(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'location_sub_element':
|
||||
Location.deleteLocationSubElement(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_world_elements':
|
||||
World.removeElementFromWorld(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_incidents':
|
||||
Incident.removeIncident(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_plot_points':
|
||||
PlotPoint.removePlotPoint(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_issues':
|
||||
Issue.removeIssue(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_spells':
|
||||
Spell.deleteSpell(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'book_spell_tags':
|
||||
Spell.deleteSpellTag(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Apply server tombstones for series entities locally.
|
||||
*/
|
||||
ipcMain.handle('db:tombstones:apply:series', createHandler<RemovedItemRecord[], void>(
|
||||
function(userId: string, tombstones: RemovedItemRecord[], lang: 'fr' | 'en'): void {
|
||||
for (const tombstone of tombstones) {
|
||||
switch (tombstone.table_name) {
|
||||
case 'erit_series':
|
||||
Series.deleteSeries(userId, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'series_books':
|
||||
Series.removeBookFromSeries(userId, tombstone.book_id!, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'series_characters':
|
||||
SeriesCharacter.deleteCharacter(userId, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'series_characters_attributes':
|
||||
SeriesCharacter.deleteAttribute(userId, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'series_locations':
|
||||
SeriesLocation.deleteLocation(userId, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'series_location_elements':
|
||||
SeriesLocation.deleteElement(userId, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'series_location_sub_elements':
|
||||
SeriesLocation.deleteSubElement(userId, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'series_world_elements':
|
||||
SeriesWorld.deleteElement(userId, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'series_spells':
|
||||
SeriesSpell.deleteSpell(userId, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
case 'series_spell_tags':
|
||||
SeriesSpell.deleteTag(userId, tombstone.entity_id, tombstone.deleted_at, lang);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -16,6 +16,13 @@ import './ipc/character.ipc.js';
|
||||
import './ipc/location.ipc.js';
|
||||
import './ipc/offline.ipc.js';
|
||||
import './ipc/spell.ipc.js';
|
||||
import './ipc/series.ipc.js';
|
||||
import './ipc/series-sync.ipc.js';
|
||||
import './ipc/series-character.ipc.js';
|
||||
import './ipc/series-location.ipc.js';
|
||||
import './ipc/series-world.ipc.js';
|
||||
import './ipc/series-spell.ipc.js';
|
||||
import './ipc/tombstone.ipc.js';
|
||||
|
||||
// Fix pour __dirname en ES modules
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
||||
Reference in New Issue
Block a user