Files
ERitors-Scribe-Desktop/electron/database/models/Act.ts
natreex cf6fb97bf0 Add models for guidelines, incidents, plot points, issues, acts, and world data
- Introduced new models: `GuideLine`, `Incident`, `PlotPoint`, `Issue`, `Act`, and `World` for managing book-related entities.
- Integrated encryption/decryption for sensitive properties in all models using user-specific keys.
- Added methods for CRUD operations and synchronization workflows with error handling and multilingual support.
- Improved maintainability with JSDoc comments and streamlined queries.
2026-01-12 13:38:10 -05:00

186 lines
8.7 KiB
TypeScript

import { getUserEncryptionKey } from "../keyManager.js";
import System from "../System.js";
import PlotPoint, { PlotPointProps, PlotPointStory } from "./PlotPoint.js";
import Incident, { IncidentProps, IncidentStory } from "./Incident.js";
import ActRepository, { ActQuery } from "../repositories/act.repository.js";
import Chapter, { ChapterProps } from "./Chapter.js";
import IncidentRepository from "../repositories/incident.repository.js";
import PlotPointRepository from "../repositories/plotpoint.repository.js";
import ChapterRepo from "../repositories/chapter.repository.js";
export interface ActProps {
id: number;
summary: string | null;
incidents?: IncidentProps[];
plotPoints?: PlotPointProps[];
chapters?: ActChapter[];
}
export interface ActStory {
actId: number;
summary: string;
chapterSummary: string;
chapterGoal: string;
incidents: IncidentStory[];
plotPoints: PlotPointStory[];
}
export interface ActChapter {
chapterInfoId: number;
chapterId: string;
title: string;
chapterOrder: number;
actId: number;
incidentId: string | null;
plotPointId: string | null;
summary: string;
goal: string;
}
export interface SyncedActSummary {
id: string;
lastUpdate: number;
}
export default class Act {
/**
* Retrieves all acts data for a specific book, including chapters, incidents, and plot points.
* Decrypts summaries using the user's encryption key.
* @param userId - The unique identifier of the user
* @param bookId - The unique identifier of the book
* @param lang - The language for localization ('fr' or 'en'), defaults to 'fr'
* @returns A promise resolving to an array of Act objects with their associated data
*/
public static async getActsData(userId: string, bookId: string, lang: 'fr' | 'en' = 'fr'): Promise<ActProps[]> {
const userEncryptionKey: string = getUserEncryptionKey(userId);
const actChapters: ActChapter[] = Chapter.getAllChapterFromActs(userId, bookId, lang);
const actQueries: ActQuery[] = ActRepository.fetchAllActs(userId, bookId, lang);
const bookIncidents: IncidentProps[] = await Incident.getIncitentsIncidents(userId, bookId, actChapters);
const bookPlotPoints: PlotPointProps[] = await PlotPoint.getPlotPoints(userId, bookId, actChapters);
const acts: ActProps[] = [];
acts.push({
id: 1,
summary: '',
chapters: actChapters.filter((chapter: ActChapter) => chapter.actId === 1)
});
acts.push({
id: 2,
summary: '',
incidents: bookIncidents ? bookIncidents : [],
});
acts.push({
id: 3,
summary: '',
plotPoints: bookPlotPoints ? bookPlotPoints : [],
});
acts.push({
id: 4,
summary: '',
chapters: actChapters.filter((chapter: ActChapter) => chapter.actId === 4)
});
acts.push({
id: 5,
summary: '',
chapters: actChapters.filter((chapter: ActChapter) => chapter.actId === 5)
});
if (actQueries.length > 0) {
for (const actQuery of actQueries) {
acts[actQuery.act_index - 1].summary = actQuery.summary && userEncryptionKey
? System.decryptDataWithUserKey(actQuery.summary, userEncryptionKey)
: '';
}
}
return acts;
}
/**
* Updates multiple acts including their summaries, incidents, plot points, and chapter information.
* Encrypts all sensitive data before storing in the database.
* @param acts - Array of act properties to update
* @param userId - The unique identifier of the user
* @param bookId - The unique identifier of the book
* @param userKey - The user's encryption key for data encryption
* @param lang - The language for localization ('fr' or 'en'), defaults to 'fr'
* @returns A promise resolving to true when all updates are complete
*/
public static async updateAct(acts: ActProps[], userId: string, bookId: string, userKey: string, lang: 'fr' | 'en' = 'fr'): Promise<boolean> {
for (const act of acts) {
const actIncidents: IncidentProps[] = act.incidents ? act.incidents : [];
const actId: number = act.id;
if (actId === 1 || actId === 4 || actId === 5) {
const encryptedActSummary: string = act.summary ? System.encryptDataWithUserKey(act.summary, userKey) : '';
try {
ActRepository.updateActSummary(userId, bookId, actId, encryptedActSummary, System.timeStampInSeconds(), lang);
} catch (error: unknown) {
const newActSummaryId: string = System.createUniqueId();
ActRepository.insertActSummary(newActSummaryId, userId, bookId, actId, encryptedActSummary, lang);
}
if (act.chapters) {
Chapter.updateChapterInfos(act.chapters, userId, actId, bookId, null, null, lang);
}
} else if (actId === 2) {
for (const incident of actIncidents) {
const encryptedIncidentSummary: string = incident.summary ? System.encryptDataWithUserKey(incident.summary, userKey) : '';
const incidentId: string = incident.incidentId;
const incidentTitle: string = incident.title;
const hashedIncidentTitle: string = System.hashElement(incidentTitle);
const encryptedIncidentTitle: string = System.encryptDataWithUserKey(incidentTitle, userKey);
IncidentRepository.updateIncident(userId, bookId, incidentId, encryptedIncidentTitle, hashedIncidentTitle, encryptedIncidentSummary, System.timeStampInSeconds(), lang);
if (incident.chapters) {
Chapter.updateChapterInfos(incident.chapters, userId, actId, bookId, incidentId, null, lang);
}
}
} else {
const actPlotPoints: PlotPointProps[] = act.plotPoints ? act.plotPoints : [];
for (const plotPoint of actPlotPoints) {
const encryptedPlotPointSummary: string = plotPoint.summary ? System.encryptDataWithUserKey(plotPoint.summary, userKey) : '';
const plotPointId: string = plotPoint.plotPointId;
const plotPointTitle: string = plotPoint.title;
const hashedPlotPointTitle: string = System.hashElement(plotPointTitle);
const encryptedPlotPointTitle: string = System.encryptDataWithUserKey(plotPointTitle, userKey);
PlotPointRepository.updatePlotPoint(userId, bookId, plotPointId, encryptedPlotPointTitle, hashedPlotPointTitle, encryptedPlotPointSummary, System.timeStampInSeconds(), lang);
if (plotPoint.chapters) {
Chapter.updateChapterInfos(plotPoint.chapters, userId, actId, bookId, null, plotPointId, lang);
}
}
}
}
return true;
}
/**
* Updates the story structure including acts and main chapters.
* Encrypts chapter titles and updates their order in the database.
* @param userId - The unique identifier of the user
* @param bookId - The unique identifier of the book
* @param acts - Array of act properties to update
* @param mainChapters - Array of main chapter properties to update
* @param lang - The language for localization ('fr' or 'en'), defaults to 'fr'
* @returns True when all updates are complete
*/
public static updateStory(userId: string, bookId: string, acts: ActProps[], mainChapters: ChapterProps[], lang: 'fr' | 'en' = 'fr'): boolean {
const userEncryptionKey: string = getUserEncryptionKey(userId);
Act.updateAct(acts, userId, bookId, userEncryptionKey, lang).then();
for (const chapter of mainChapters) {
const chapterId: string = chapter.chapterId;
const chapterTitle: string = chapter.title;
const hashedChapterTitle: string = System.hashElement(chapterTitle);
const encryptedChapterTitle: string = System.encryptDataWithUserKey(chapterTitle, userEncryptionKey);
const chapterOrder: number = chapter.chapterOrder;
ChapterRepo.updateChapter(userId, chapterId, encryptedChapterTitle, hashedChapterTitle, chapterOrder, System.timeStampInSeconds(), lang);
}
return true;
}
}