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:
natreex
2026-02-05 14:12:08 -05:00
parent cec5830360
commit 209dc6f85a
133 changed files with 17673 additions and 3110 deletions

View File

@@ -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 {

View File

@@ -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;
}
/**

View File

@@ -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;
}
/**

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
/**

View File

@@ -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;
}
}

View 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
);
}
}

View File

@@ -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;
}
/**

View File

@@ -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;
}
/**

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
};
});
}
}

View File

@@ -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,

View File

@@ -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;
}
}