Add deletedAt timestamps to delete operations for better audit tracking
- Updated delete methods across hooks and components to include `deletedAt: System.timeStampInSeconds()`. - Refactored synchronized delete logic to pass `deletedAt` for both offline and online states. - Improved synchronization workflows to include `deletedAt` in server and IPC requests. - Enhanced destructuring patterns for cleaner and more consistent request data.
This commit is contained in:
62
app/page.tsx
62
app/page.tsx
@@ -1,5 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import {useContext, useEffect, useState} from 'react';
|
import {useCallback, useContext, useEffect, useRef, useState} from 'react';
|
||||||
import {BookContext} from "@/context/BookContext";
|
import {BookContext} from "@/context/BookContext";
|
||||||
import {ChapterProps} from "@/lib/models/Chapter";
|
import {ChapterProps} from "@/lib/models/Chapter";
|
||||||
import {ChapterContext} from '@/context/ChapterContext';
|
import {ChapterContext} from '@/context/ChapterContext';
|
||||||
@@ -67,59 +67,57 @@ const messagesMap = {
|
|||||||
|
|
||||||
function AutoSyncOnReconnect() {
|
function AutoSyncOnReconnect() {
|
||||||
const {offlineMode} = useContext(OfflineContext);
|
const {offlineMode} = useContext(OfflineContext);
|
||||||
const {syncAllToServer: syncAllBooksToServer, refreshBooks, booksToSyncToServer} = useSyncBooks();
|
const {session} = useContext(SessionContext);
|
||||||
const {syncAllToServer: syncAllSeriesToServer, refreshSeries, seriesToSyncToServer} = useSyncSeries();
|
const {syncAllToServer: syncAllBooksToServer, syncAllFromServer: syncAllBooksFromServer, refreshBooks, booksToSyncToServer, booksToSyncFromServer} = useSyncBooks();
|
||||||
const [pendingSync, setPendingSync] = useState<boolean>(false);
|
const {syncAllToServer: syncAllSeriesToServer, syncAllFromServer: syncAllSeriesFromServer, refreshSeries, seriesToSyncToServer, seriesToSyncFromServer} = useSyncSeries();
|
||||||
const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
|
const isSyncingRef = useRef<boolean>(false);
|
||||||
|
const hasRefreshedRef = useRef<boolean>(false);
|
||||||
|
|
||||||
const saveLastOnlineTimestamp = (): void => {
|
const saveLastOnlineTimestamp = useCallback((): void => {
|
||||||
const timestamp: number = Math.floor(Date.now() / 1000);
|
const timestamp: number = Math.floor(Date.now() / 1000);
|
||||||
localStorage.setItem('lastOnlineTimestamp', timestamp.toString());
|
localStorage.setItem('lastOnlineTimestamp', timestamp.toString());
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
|
// Refresh sync data when online + authenticated + DB ready
|
||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
if (!offlineMode.isOffline) {
|
if (!offlineMode.isOffline && session.isConnected && offlineMode.isDatabaseInitialized) {
|
||||||
setPendingSync(true);
|
hasRefreshedRef.current = true;
|
||||||
setIsRefreshing(true);
|
Promise.all([refreshBooks(), refreshSeries()]);
|
||||||
Promise.all([refreshBooks(), refreshSeries()]).then(() => {
|
|
||||||
setIsRefreshing(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [offlineMode.isOffline]);
|
}, [offlineMode.isOffline, session.isConnected, offlineMode.isDatabaseInitialized]);
|
||||||
|
|
||||||
|
// Auto-sync when diffs become available (reactive, no flags)
|
||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
if (pendingSync && !isRefreshing) {
|
if (offlineMode.isOffline || !session.isConnected || isSyncingRef.current || !hasRefreshedRef.current) return;
|
||||||
|
|
||||||
const syncPromises: Promise<void>[] = [];
|
const syncPromises: Promise<void>[] = [];
|
||||||
|
|
||||||
if (booksToSyncToServer.length > 0) {
|
if (booksToSyncToServer.length > 0) syncPromises.push(syncAllBooksToServer());
|
||||||
syncPromises.push(syncAllBooksToServer());
|
if (booksToSyncFromServer.length > 0) syncPromises.push(syncAllBooksFromServer());
|
||||||
}
|
if (seriesToSyncToServer.length > 0) syncPromises.push(syncAllSeriesToServer());
|
||||||
if (seriesToSyncToServer.length > 0) {
|
if (seriesToSyncFromServer.length > 0) syncPromises.push(syncAllSeriesFromServer());
|
||||||
syncPromises.push(syncAllSeriesToServer());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (syncPromises.length > 0) {
|
if (syncPromises.length > 0) {
|
||||||
Promise.all(syncPromises).then(() => {
|
isSyncingRef.current = true;
|
||||||
|
Promise.all(syncPromises).then((): void => {
|
||||||
saveLastOnlineTimestamp();
|
saveLastOnlineTimestamp();
|
||||||
|
isSyncingRef.current = false;
|
||||||
|
}).catch((): void => {
|
||||||
|
isSyncingRef.current = false;
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
saveLastOnlineTimestamp();
|
|
||||||
}
|
}
|
||||||
|
}, [booksToSyncToServer, booksToSyncFromServer, seriesToSyncToServer, seriesToSyncFromServer]);
|
||||||
setPendingSync(false);
|
|
||||||
}
|
|
||||||
}, [booksToSyncToServer, seriesToSyncToServer, pendingSync, isRefreshing]);
|
|
||||||
|
|
||||||
// Update lastOnlineTimestamp every 5 minutes while online
|
// Update lastOnlineTimestamp every 5 minutes while online
|
||||||
useEffect((): (() => void) | void => {
|
useEffect((): (() => void) | void => {
|
||||||
if (!offlineMode.isOffline) {
|
if (!offlineMode.isOffline && session.isConnected) {
|
||||||
const intervalId: NodeJS.Timeout = setInterval((): void => {
|
const intervalId: NodeJS.Timeout = setInterval((): void => {
|
||||||
saveLastOnlineTimestamp();
|
saveLastOnlineTimestamp();
|
||||||
}, 5 * 60 * 1000); // 5 minutes
|
}, 5 * 60 * 1000);
|
||||||
|
|
||||||
return (): void => clearInterval(intervalId);
|
return (): void => clearInterval(intervalId);
|
||||||
}
|
}
|
||||||
}, [offlineMode.isOffline]);
|
}, [offlineMode.isOffline, session.isConnected, saveLastOnlineTimestamp]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -284,8 +282,6 @@ function ScribeContent() {
|
|||||||
|
|
||||||
useEffect((): void => {
|
useEffect((): void => {
|
||||||
if (session.isConnected) {
|
if (session.isConnected) {
|
||||||
refreshBooks().then()
|
|
||||||
refreshSeries().then()
|
|
||||||
setIsTermsAccepted(session.user?.termsAccepted ?? false);
|
setIsTermsAccepted(session.user?.termsAccepted ?? false);
|
||||||
setHomeStepsGuide(User.guideTourDone(session.user?.guideTour ?? [], 'home-basic'));
|
setHomeStepsGuide(User.guideTourDone(session.user?.guideTour ?? [], 'home-basic'));
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {faCloud, faCloudArrowDown, faCloudArrowUp, faSpinner} from "@fortawesome/free-solid-svg-icons";
|
import {faCloud, faCloudArrowDown, faCloudArrowUp, faSpinner} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {useTranslations} from "next-intl";
|
import {useTranslations} from "next-intl";
|
||||||
import {useState, useContext} from "react";
|
import {useState, useEffect, useContext} from "react";
|
||||||
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
|
import OfflineContext, {OfflineContextType} from "@/context/OfflineContext";
|
||||||
import {SyncType} from "@/context/BooksSyncContext";
|
import {SyncType} from "@/context/BooksSyncContext";
|
||||||
import useSyncBooks from "@/hooks/useSyncBooks";
|
import useSyncBooks from "@/hooks/useSyncBooks";
|
||||||
@@ -18,6 +18,10 @@ export default function SyncBook({bookId, status}: SyncBookProps) {
|
|||||||
const [currentStatus, setCurrentStatus] = useState<SyncType>(status);
|
const [currentStatus, setCurrentStatus] = useState<SyncType>(status);
|
||||||
const {upload: hookUpload, download: hookDownload, syncFromServer: hookSyncFromServer, syncToServer: hookSyncToServer} = useSyncBooks();
|
const {upload: hookUpload, download: hookDownload, syncFromServer: hookSyncFromServer, syncToServer: hookSyncToServer} = useSyncBooks();
|
||||||
|
|
||||||
|
useEffect((): void => {
|
||||||
|
setCurrentStatus(status);
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
const isOffline: boolean = isCurrentlyOffline();
|
const isOffline: boolean = isCurrentlyOffline();
|
||||||
|
|
||||||
async function upload(): Promise<void> {
|
async function upload(): Promise<void> {
|
||||||
|
|||||||
@@ -486,8 +486,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
|
|||||||
let response: boolean;
|
let response: boolean;
|
||||||
const elementId = sections.find((section: LocationProps): boolean => section.id === sectionId)
|
const elementId = sections.find((section: LocationProps): boolean => section.id === sectionId)
|
||||||
?.elements[elementIndex].id;
|
?.elements[elementIndex].id;
|
||||||
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
const deleteData = {elementId: elementId};
|
const deleteData = {elementId: elementId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
response = await window.electron.invoke<boolean>('db:series:location:element:delete', deleteData);
|
response = await window.electron.invoke<boolean>('db:series:location:element:delete', deleteData);
|
||||||
} else {
|
} else {
|
||||||
@@ -498,16 +499,16 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
|
|||||||
}
|
}
|
||||||
} else if (isCurrentlyOffline() || book?.localBook) {
|
} else if (isCurrentlyOffline() || book?.localBook) {
|
||||||
response = await window.electron.invoke<boolean>('db:location:element:delete', {
|
response = await window.electron.invoke<boolean>('db:location:element:delete', {
|
||||||
elementId: elementId,
|
elementId: elementId, bookId: currentEntityId, deletedAt,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
response = await System.authDeleteToServer<boolean>(`location/element/delete`, {
|
response = await System.authDeleteToServer<boolean>(`location/element/delete`, {
|
||||||
elementId: elementId,
|
elementId: elementId, bookId: currentEntityId, deletedAt,
|
||||||
}, token, lang);
|
}, token, lang);
|
||||||
|
|
||||||
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === currentEntityId)) {
|
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === currentEntityId)) {
|
||||||
addToQueue('db:location:element:delete', {
|
addToQueue('db:location:element:delete', {
|
||||||
elementId: elementId,
|
elementId: elementId, bookId: currentEntityId, deletedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -536,8 +537,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
|
|||||||
try {
|
try {
|
||||||
let response: boolean;
|
let response: boolean;
|
||||||
const subElementId = sections.find((section: LocationProps): boolean => section.id === sectionId)?.elements[elementIndex].subElements[subElementIndex].id;
|
const subElementId = sections.find((section: LocationProps): boolean => section.id === sectionId)?.elements[elementIndex].subElements[subElementIndex].id;
|
||||||
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
const deleteData = {subElementId: subElementId};
|
const deleteData = {subElementId: subElementId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
response = await window.electron.invoke<boolean>('db:series:location:subelement:delete', deleteData);
|
response = await window.electron.invoke<boolean>('db:series:location:subelement:delete', deleteData);
|
||||||
} else {
|
} else {
|
||||||
@@ -548,16 +550,16 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
|
|||||||
}
|
}
|
||||||
} else if (isCurrentlyOffline() || book?.localBook) {
|
} else if (isCurrentlyOffline() || book?.localBook) {
|
||||||
response = await window.electron.invoke<boolean>('db:location:subelement:delete', {
|
response = await window.electron.invoke<boolean>('db:location:subelement:delete', {
|
||||||
subElementId: subElementId,
|
subElementId: subElementId, bookId: currentEntityId, deletedAt,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
response = await System.authDeleteToServer<boolean>(`location/sub-element/delete`, {
|
response = await System.authDeleteToServer<boolean>(`location/sub-element/delete`, {
|
||||||
subElementId: subElementId,
|
subElementId: subElementId, bookId: currentEntityId, deletedAt,
|
||||||
}, token, lang);
|
}, token, lang);
|
||||||
|
|
||||||
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === currentEntityId)) {
|
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === currentEntityId)) {
|
||||||
addToQueue('db:location:subelement:delete', {
|
addToQueue('db:location:subelement:delete', {
|
||||||
subElementId: subElementId,
|
subElementId: subElementId, bookId: currentEntityId, deletedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -581,8 +583,9 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
|
|||||||
async function handleRemoveSection(sectionId: string): Promise<void> {
|
async function handleRemoveSection(sectionId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let response: boolean;
|
let response: boolean;
|
||||||
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
const deleteData = {locationId: sectionId};
|
const deleteData = {locationId: sectionId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
response = await window.electron.invoke<boolean>('db:series:location:delete', deleteData);
|
response = await window.electron.invoke<boolean>('db:series:location:delete', deleteData);
|
||||||
} else {
|
} else {
|
||||||
@@ -593,16 +596,16 @@ export function LocationComponent(props: LocationComponentProps, ref: React.Ref<
|
|||||||
}
|
}
|
||||||
} else if (isCurrentlyOffline() || book?.localBook) {
|
} else if (isCurrentlyOffline() || book?.localBook) {
|
||||||
response = await window.electron.invoke<boolean>('db:location:delete', {
|
response = await window.electron.invoke<boolean>('db:location:delete', {
|
||||||
locationId: sectionId,
|
locationId: sectionId, bookId: currentEntityId, deletedAt,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
response = await System.authDeleteToServer<boolean>(`location/delete`, {
|
response = await System.authDeleteToServer<boolean>(`location/delete`, {
|
||||||
locationId: sectionId,
|
locationId: sectionId, bookId: currentEntityId, deletedAt,
|
||||||
}, token, lang);
|
}, token, lang);
|
||||||
|
|
||||||
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === currentEntityId)) {
|
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === currentEntityId)) {
|
||||||
addToQueue('db:location:delete', {
|
addToQueue('db:location:delete', {
|
||||||
locationId: sectionId,
|
locationId: sectionId, bookId: currentEntityId, deletedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
|
|||||||
async function deleteIncident(actId: number, incidentId: string): Promise<void> {
|
async function deleteIncident(actId: number, incidentId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let response: boolean;
|
let response: boolean;
|
||||||
const deleteData = { bookId, incidentId };
|
const deleteData = { bookId, incidentId, deletedAt: System.timeStampInSeconds() };
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
response = await window.electron.invoke<boolean>('db:book:incident:remove', deleteData);
|
response = await window.electron.invoke<boolean>('db:book:incident:remove', deleteData);
|
||||||
} else {
|
} else {
|
||||||
@@ -223,7 +223,7 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
|
|||||||
async function deletePlotPoint(actId: number, plotPointId: string): Promise<void> {
|
async function deletePlotPoint(actId: number, plotPointId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let response: boolean;
|
let response: boolean;
|
||||||
const deleteData = { plotId: plotPointId };
|
const deleteData = { plotId: plotPointId, bookId, deletedAt: System.timeStampInSeconds() };
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
response = await window.electron.invoke<boolean>('db:book:plot:remove', deleteData);
|
response = await window.electron.invoke<boolean>('db:book:plot:remove', deleteData);
|
||||||
} else {
|
} else {
|
||||||
@@ -365,7 +365,7 @@ export default function Act({acts, setActs, mainChapters}: ActProps) {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let response: boolean;
|
let response: boolean;
|
||||||
const unlinkData = { chapterInfoId };
|
const unlinkData = { chapterInfoId, bookId, deletedAt: System.timeStampInSeconds() };
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
response = await window.electron.invoke<boolean>('db:chapter:information:remove', unlinkData);
|
response = await window.electron.invoke<boolean>('db:chapter:information:remove', unlinkData);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -88,10 +88,12 @@ export default function Issues({issues, setIssues}: IssuesProps) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
let response: boolean;
|
let response: boolean;
|
||||||
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
response = await window.electron.invoke<boolean>('db:book:issue:remove', {
|
response = await window.electron.invoke<boolean>('db:book:issue:remove', {
|
||||||
bookId,
|
bookId,
|
||||||
issueId,
|
issueId,
|
||||||
|
deletedAt,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
response = await System.authDeleteToServer<boolean>(
|
response = await System.authDeleteToServer<boolean>(
|
||||||
@@ -99,6 +101,7 @@ export default function Issues({issues, setIssues}: IssuesProps) {
|
|||||||
{
|
{
|
||||||
bookId,
|
bookId,
|
||||||
issueId,
|
issueId,
|
||||||
|
deletedAt,
|
||||||
},
|
},
|
||||||
token,
|
token,
|
||||||
lang
|
lang
|
||||||
@@ -108,6 +111,7 @@ export default function Issues({issues, setIssues}: IssuesProps) {
|
|||||||
addToQueue('db:book:issue:remove', {
|
addToQueue('db:book:issue:remove', {
|
||||||
bookId,
|
bookId,
|
||||||
issueId,
|
issueId,
|
||||||
|
deletedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,8 +65,9 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World
|
|||||||
try {
|
try {
|
||||||
let response: boolean;
|
let response: boolean;
|
||||||
const elementId = (worlds[selectedWorldIndex][section] as WorldElement[])[index].id;
|
const elementId = (worlds[selectedWorldIndex][section] as WorldElement[])[index].id;
|
||||||
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
const deleteData = {elementId: elementId};
|
const deleteData = {elementId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
response = await window.electron.invoke<boolean>('db:series:world:element:delete', deleteData);
|
response = await window.electron.invoke<boolean>('db:series:world:element:delete', deleteData);
|
||||||
} else {
|
} else {
|
||||||
@@ -77,16 +78,16 @@ export default function WorldElementComponent({sectionLabel, sectionType}: World
|
|||||||
}
|
}
|
||||||
} else if (isCurrentlyOffline() || book?.localBook) {
|
} else if (isCurrentlyOffline() || book?.localBook) {
|
||||||
response = await window.electron.invoke<boolean>('db:book:world:element:remove', {
|
response = await window.electron.invoke<boolean>('db:book:world:element:remove', {
|
||||||
elementId: elementId,
|
elementId, bookId: book?.bookId, deletedAt,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
response = await System.authDeleteToServer<boolean>('book/world/element/delete', {
|
response = await System.authDeleteToServer<boolean>('book/world/element/delete', {
|
||||||
elementId: elementId,
|
elementId, bookId: book?.bookId, deletedAt,
|
||||||
}, session.accessToken, lang);
|
}, session.accessToken, lang);
|
||||||
|
|
||||||
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === book?.bookId)) {
|
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === book?.bookId)) {
|
||||||
addToQueue('db:book:world:element:remove', {
|
addToQueue('db:book:world:element:remove', {
|
||||||
elementId: elementId,
|
elementId: elementId, bookId: book?.bookId, deletedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,16 +188,25 @@ export default function ScribeChapterComponent() {
|
|||||||
try {
|
try {
|
||||||
setDeleteConfirmationMessage(false);
|
setDeleteConfirmationMessage(false);
|
||||||
let response:boolean = false;
|
let response:boolean = false;
|
||||||
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
response = await window.electron.invoke<boolean>('db:chapter:remove', removeChapterId);
|
response = await window.electron.invoke<boolean>('db:chapter:remove', {
|
||||||
|
chapterId: removeChapterId,
|
||||||
|
bookId: book?.bookId,
|
||||||
|
deletedAt,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
response = await System.authDeleteToServer<boolean>('chapter/remove', {
|
response = await System.authDeleteToServer<boolean>('chapter/remove', {
|
||||||
chapterId: removeChapterId,
|
chapterId: removeChapterId,
|
||||||
|
bookId: book?.bookId,
|
||||||
|
deletedAt,
|
||||||
}, userToken, lang);
|
}, userToken, lang);
|
||||||
|
|
||||||
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === book?.bookId)) {
|
if (localSyncedBooks.find((syncedBook: SyncedBook): boolean => syncedBook.id === book?.bookId)) {
|
||||||
addToQueue('db:chapter:remove', {
|
addToQueue('db:chapter:remove', {
|
||||||
chapterId: removeChapterId,
|
chapterId: removeChapterId,
|
||||||
|
bookId: book?.bookId,
|
||||||
|
deletedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default function SeriesSettingSidebar(
|
|||||||
|
|
||||||
async function handleDeleteSeries(): Promise<void> {
|
async function handleDeleteSeries(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const deleteData = {seriesId: seriesId};
|
const deleteData = {seriesId: seriesId, deletedAt: System.timeStampInSeconds()};
|
||||||
let success: boolean;
|
let success: boolean;
|
||||||
|
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
|
|||||||
@@ -174,7 +174,8 @@ function SeriesBooksManager(props: object, ref: React.Ref<{ handleSave: () => Pr
|
|||||||
try {
|
try {
|
||||||
const removeData = {
|
const removeData = {
|
||||||
seriesId: seriesId,
|
seriesId: seriesId,
|
||||||
bookId: bookId
|
bookId: bookId,
|
||||||
|
deletedAt: System.timeStampInSeconds(),
|
||||||
};
|
};
|
||||||
let response: boolean;
|
let response: boolean;
|
||||||
|
|
||||||
|
|||||||
@@ -523,10 +523,11 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
|
|||||||
const deleteCharacter = useCallback(async function (characterId: string): Promise<void> {
|
const deleteCharacter = useCallback(async function (characterId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let response: boolean;
|
let response: boolean;
|
||||||
const requestData = {characterId: characterId};
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
|
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
// Series mode - dual logic
|
// Series mode - dual logic
|
||||||
|
const requestData = {characterId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
response = await window.electron.invoke<boolean>('db:series:character:delete', requestData);
|
response = await window.electron.invoke<boolean>('db:series:character:delete', requestData);
|
||||||
} else {
|
} else {
|
||||||
@@ -537,6 +538,7 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Pattern A: mutations
|
// Pattern A: mutations
|
||||||
|
const requestData = {characterId, bookId: entityId, deletedAt};
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
// Offline OU livre local → IPC
|
// Offline OU livre local → IPC
|
||||||
response = await window.electron.invoke<boolean>('db:character:delete', requestData);
|
response = await window.electron.invoke<boolean>('db:character:delete', requestData);
|
||||||
@@ -649,11 +651,12 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
|
|||||||
setSelectedCharacter({...selectedCharacter, [section]: updatedSection});
|
setSelectedCharacter({...selectedCharacter, [section]: updatedSection});
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const requestData = {attributeId: attrId};
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
|
|
||||||
let response: boolean;
|
let response: boolean;
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
// Series mode - dual logic
|
// Series mode - dual logic
|
||||||
|
const requestData = {attributeId: attrId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
response = await window.electron.invoke<boolean>('db:series:character:attribute:delete', requestData);
|
response = await window.electron.invoke<boolean>('db:series:character:attribute:delete', requestData);
|
||||||
} else {
|
} else {
|
||||||
@@ -664,6 +667,7 @@ export function useCharacters(config: UseCharactersConfig): UseCharactersReturn
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Pattern A: mutations
|
// Pattern A: mutations
|
||||||
|
const requestData = {attributeId: attrId, bookId: entityId, deletedAt};
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
// Offline OU livre local → IPC
|
// Offline OU livre local → IPC
|
||||||
response = await window.electron.invoke<boolean>('db:character:attribute:delete', requestData);
|
response = await window.electron.invoke<boolean>('db:character:attribute:delete', requestData);
|
||||||
|
|||||||
@@ -514,9 +514,10 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn {
|
|||||||
const removeSection = useCallback(async function (sectionId: string): Promise<void> {
|
const removeSection = useCallback(async function (sectionId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let success: boolean;
|
let success: boolean;
|
||||||
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
// Series mode - dual logic
|
// Series mode - dual logic
|
||||||
const deleteData = {locationId: sectionId};
|
const deleteData = {locationId: sectionId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
success = await window.electron.invoke<boolean>('db:series:location:delete', deleteData);
|
success = await window.electron.invoke<boolean>('db:series:location:delete', deleteData);
|
||||||
} else {
|
} else {
|
||||||
@@ -527,7 +528,7 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const requestData = {
|
const requestData = {
|
||||||
locationId: sectionId,
|
locationId: sectionId, bookId: entityId, deletedAt,
|
||||||
};
|
};
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
success = await window.electron.invoke<boolean>('db:location:delete', requestData);
|
success = await window.electron.invoke<boolean>('db:location:delete', requestData);
|
||||||
@@ -563,9 +564,10 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn {
|
|||||||
return section.id === sectionId;
|
return section.id === sectionId;
|
||||||
})?.elements[elementIndex].id;
|
})?.elements[elementIndex].id;
|
||||||
let success: boolean;
|
let success: boolean;
|
||||||
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
// Series mode - dual logic
|
// Series mode - dual logic
|
||||||
const deleteData = {elementId: elementId};
|
const deleteData = {elementId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
success = await window.electron.invoke<boolean>('db:series:location:element:delete', deleteData);
|
success = await window.electron.invoke<boolean>('db:series:location:element:delete', deleteData);
|
||||||
} else {
|
} else {
|
||||||
@@ -576,7 +578,7 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const requestData = {
|
const requestData = {
|
||||||
elementId: elementId,
|
elementId, bookId: entityId, deletedAt,
|
||||||
};
|
};
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
success = await window.electron.invoke<boolean>('db:location:element:delete', requestData);
|
success = await window.electron.invoke<boolean>('db:location:element:delete', requestData);
|
||||||
@@ -615,9 +617,10 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn {
|
|||||||
return section.id === sectionId;
|
return section.id === sectionId;
|
||||||
})?.elements[elementIndex].subElements[subElementIndex].id;
|
})?.elements[elementIndex].subElements[subElementIndex].id;
|
||||||
let success: boolean;
|
let success: boolean;
|
||||||
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
// Series mode - dual logic
|
// Series mode - dual logic
|
||||||
const deleteData = {subElementId: subElementId};
|
const deleteData = {subElementId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
success = await window.electron.invoke<boolean>('db:series:location:subelement:delete', deleteData);
|
success = await window.electron.invoke<boolean>('db:series:location:subelement:delete', deleteData);
|
||||||
} else {
|
} else {
|
||||||
@@ -628,7 +631,7 @@ export function useLocations(config: UseLocationsConfig): UseLocationsReturn {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const requestData = {
|
const requestData = {
|
||||||
subElementId: subElementId,
|
subElementId, bookId: entityId, deletedAt,
|
||||||
};
|
};
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
success = await window.electron.invoke<boolean>('db:location:subelement:delete', requestData);
|
success = await window.electron.invoke<boolean>('db:location:subelement:delete', requestData);
|
||||||
|
|||||||
@@ -296,12 +296,20 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.seriesSpellId) {
|
if (response.seriesSpellId) {
|
||||||
const seriesSpellResponse: SeriesSpellDetailResponse = await System.authGetQueryToServer<SeriesSpellDetailResponse>(
|
let seriesSpellResponse: SeriesSpellDetailResponse;
|
||||||
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
|
seriesSpellResponse = await window.electron.invoke<SeriesSpellDetailResponse>(
|
||||||
|
'db:series:spell:detail',
|
||||||
|
{spellId: response.seriesSpellId}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
seriesSpellResponse = await System.authGetQueryToServer<SeriesSpellDetailResponse>(
|
||||||
'series/spell/detail',
|
'series/spell/detail',
|
||||||
userToken,
|
userToken,
|
||||||
lang,
|
lang,
|
||||||
{spellid: response.seriesSpellId}
|
{spellid: response.seriesSpellId}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
if (seriesSpellResponse) {
|
if (seriesSpellResponse) {
|
||||||
setSelectedSeriesSpell(seriesSpellResponse);
|
setSelectedSeriesSpell(seriesSpellResponse);
|
||||||
}
|
}
|
||||||
@@ -536,9 +544,10 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn {
|
|||||||
const deleteSpell = useCallback(async function (spellId: string): Promise<void> {
|
const deleteSpell = useCallback(async function (spellId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let success: boolean;
|
let success: boolean;
|
||||||
const requestData = {spellId};
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
// Series mode - dual logic
|
// Series mode - dual logic
|
||||||
|
const requestData = {spellId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
success = await window.electron.invoke<boolean>('db:series:spell:delete', requestData);
|
success = await window.electron.invoke<boolean>('db:series:spell:delete', requestData);
|
||||||
} else {
|
} else {
|
||||||
@@ -548,6 +557,7 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const requestData = {spellId, bookId: entityId, deletedAt};
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
success = await window.electron.invoke<boolean>('db:spell:delete', requestData);
|
success = await window.electron.invoke<boolean>('db:spell:delete', requestData);
|
||||||
} else {
|
} else {
|
||||||
@@ -844,9 +854,10 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn {
|
|||||||
const deleteTag = useCallback(async function (tagId: string): Promise<boolean> {
|
const deleteTag = useCallback(async function (tagId: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
let success: boolean;
|
let success: boolean;
|
||||||
|
const deletedAt: number = System.timeStampInSeconds();
|
||||||
if (isSeriesMode) {
|
if (isSeriesMode) {
|
||||||
// Series mode - dual logic
|
// Series mode - dual logic
|
||||||
const deleteData = {tagId};
|
const deleteData = {tagId, deletedAt};
|
||||||
if (isCurrentlyOffline() || localSeries) {
|
if (isCurrentlyOffline() || localSeries) {
|
||||||
success = await window.electron.invoke<boolean>('db:series:spell:tag:delete', deleteData);
|
success = await window.electron.invoke<boolean>('db:series:spell:tag:delete', deleteData);
|
||||||
} else {
|
} else {
|
||||||
@@ -856,7 +867,7 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const requestData = {tagId, bookId: entityId};
|
const requestData = {tagId, bookId: entityId, deletedAt};
|
||||||
if (isCurrentlyOffline() || book?.localBook) {
|
if (isCurrentlyOffline() || book?.localBook) {
|
||||||
success = await window.electron.invoke<boolean>('db:spell:tag:delete', requestData);
|
success = await window.electron.invoke<boolean>('db:spell:tag:delete', requestData);
|
||||||
} else {
|
} else {
|
||||||
@@ -885,17 +896,25 @@ export function useSpells(config: UseSpellsConfig): UseSpellsReturn {
|
|||||||
|
|
||||||
const handleSyncComplete = useCallback(async function (): Promise<void> {
|
const handleSyncComplete = useCallback(async function (): Promise<void> {
|
||||||
if (selectedSpell?.seriesSpellId) {
|
if (selectedSpell?.seriesSpellId) {
|
||||||
const seriesSpellResponse: SeriesSpellDetailResponse = await System.authGetQueryToServer<SeriesSpellDetailResponse>(
|
let seriesSpellResponse: SeriesSpellDetailResponse;
|
||||||
|
if (isCurrentlyOffline() || (isSeriesMode ? localSeries : book?.localBook)) {
|
||||||
|
seriesSpellResponse = await window.electron.invoke<SeriesSpellDetailResponse>(
|
||||||
|
'db:series:spell:detail',
|
||||||
|
{spellId: selectedSpell.seriesSpellId}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
seriesSpellResponse = await System.authGetQueryToServer<SeriesSpellDetailResponse>(
|
||||||
'series/spell/detail',
|
'series/spell/detail',
|
||||||
userToken,
|
userToken,
|
||||||
lang,
|
lang,
|
||||||
{spellid: selectedSpell.seriesSpellId}
|
{spellid: selectedSpell.seriesSpellId}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
if (seriesSpellResponse) {
|
if (seriesSpellResponse) {
|
||||||
setSelectedSeriesSpell(seriesSpellResponse);
|
setSelectedSeriesSpell(seriesSpellResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [selectedSpell?.seriesSpellId, userToken, lang]);
|
}, [selectedSpell?.seriesSpellId, userToken, lang, isCurrentlyOffline, isSeriesMode, localSeries, book?.localBook]);
|
||||||
|
|
||||||
const enterDetailMode = useCallback(async function (spell: SpellListItem): Promise<void> {
|
const enterDetailMode = useCallback(async function (spell: SpellListItem): Promise<void> {
|
||||||
await selectSpell(spell);
|
await selectSpell(spell);
|
||||||
|
|||||||
@@ -186,6 +186,12 @@ export default function useSyncBooks() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function syncAllFromServer(): Promise<void> {
|
||||||
|
for (const diff of booksToSyncFromServer) {
|
||||||
|
await syncFromServer(diff.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshBooks(): Promise<void> {
|
async function refreshBooks(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let localBooksResponse: SyncedBook[] = [];
|
let localBooksResponse: SyncedBook[] = [];
|
||||||
@@ -250,6 +256,7 @@ export default function useSyncBooks() {
|
|||||||
syncFromServer,
|
syncFromServer,
|
||||||
syncToServer,
|
syncToServer,
|
||||||
syncAllToServer,
|
syncAllToServer,
|
||||||
|
syncAllFromServer,
|
||||||
refreshBooks,
|
refreshBooks,
|
||||||
localOnlyBooks,
|
localOnlyBooks,
|
||||||
serverOnlyBooks,
|
serverOnlyBooks,
|
||||||
|
|||||||
@@ -275,10 +275,12 @@ export default function useSyncSeries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function syncAllFromServer(): Promise<void> {
|
||||||
* Refreshes the sync status of all series by comparing local and server data.
|
for (const diff of seriesToSyncFromServer) {
|
||||||
* Updates the context with the latest sync information.
|
await syncFromServer(diff.id);
|
||||||
*/
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshSeries(): Promise<void> {
|
async function refreshSeries(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let localSeriesResponse: SyncedSeries[] = [];
|
let localSeriesResponse: SyncedSeries[] = [];
|
||||||
@@ -343,6 +345,7 @@ export default function useSyncSeries() {
|
|||||||
syncFromServer,
|
syncFromServer,
|
||||||
syncToServer,
|
syncToServer,
|
||||||
syncAllToServer,
|
syncAllToServer,
|
||||||
|
syncAllFromServer,
|
||||||
refreshSeries,
|
refreshSeries,
|
||||||
localOnlySeries,
|
localOnlySeries,
|
||||||
serverOnlySeries,
|
serverOnlySeries,
|
||||||
|
|||||||
Reference in New Issue
Block a user