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:
352
hooks/useSyncSeries.ts
Normal file
352
hooks/useSyncSeries.ts
Normal file
@@ -0,0 +1,352 @@
|
||||
import { useContext } from 'react';
|
||||
import System from '@/lib/models/System';
|
||||
import { SessionContext } from '@/context/SessionContext';
|
||||
import { LangContext } from '@/context/LangContext';
|
||||
import { AlertContext } from '@/context/AlertContext';
|
||||
import OfflineContext from '@/context/OfflineContext';
|
||||
import { SeriesSyncContext } from '@/context/SeriesSyncContext';
|
||||
import { SeriesSyncCompare, SyncedSeries } from '@/lib/models/SyncedSeries';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
interface RemovedItemRecord {
|
||||
removal_id: string;
|
||||
table_name: string;
|
||||
entity_id: string;
|
||||
book_id: string | null;
|
||||
user_id: string;
|
||||
deleted_at: number;
|
||||
}
|
||||
|
||||
interface SyncedSeriesResponse {
|
||||
series: SyncedSeries[];
|
||||
tombstones: RemovedItemRecord[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete series data structure for full upload/download operations.
|
||||
* Mirrors the backend CompleteSeries interface.
|
||||
*/
|
||||
interface CompleteSeries {
|
||||
series: unknown[];
|
||||
seriesBooks: unknown[];
|
||||
seriesCharacters: unknown[];
|
||||
seriesCharacterAttributes: unknown[];
|
||||
seriesWorlds: unknown[];
|
||||
seriesWorldElements: unknown[];
|
||||
seriesLocations: unknown[];
|
||||
seriesLocationElements: unknown[];
|
||||
seriesLocationSubElements: unknown[];
|
||||
seriesSpells: unknown[];
|
||||
seriesSpellTags: unknown[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing series synchronization between local database and server.
|
||||
* Provides methods for upload, download, and partial sync operations.
|
||||
*/
|
||||
export default function useSyncSeries() {
|
||||
const t = useTranslations();
|
||||
const { session } = useContext(SessionContext);
|
||||
const { lang } = useContext(LangContext);
|
||||
const { errorMessage } = useContext(AlertContext);
|
||||
const { isCurrentlyOffline, offlineMode } = useContext(OfflineContext);
|
||||
const {
|
||||
seriesToSyncToServer,
|
||||
seriesToSyncFromServer,
|
||||
localOnlySeries,
|
||||
serverOnlySeries,
|
||||
setLocalOnlySeries,
|
||||
setServerOnlySeries,
|
||||
setServerSyncedSeries,
|
||||
setLocalSyncedSeries,
|
||||
setSeriesToSyncFromServer,
|
||||
setSeriesToSyncToServer
|
||||
} = useContext(SeriesSyncContext);
|
||||
|
||||
/**
|
||||
* Uploads a local-only series to the server.
|
||||
* @param seriesId - The ID of the series to upload
|
||||
* @returns True if upload was successful, false otherwise
|
||||
*/
|
||||
async function upload(seriesId: string): Promise<boolean> {
|
||||
if (isCurrentlyOffline()) return false;
|
||||
|
||||
try {
|
||||
const seriesToSync: CompleteSeries = await window.electron.invoke<CompleteSeries>('db:series:uploadToServer', seriesId);
|
||||
if (!seriesToSync) {
|
||||
errorMessage(t('seriesCard.uploadError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const response: boolean = await System.authPostToServer('series/sync/upload', {
|
||||
series: seriesToSync
|
||||
}, session.accessToken, lang);
|
||||
|
||||
if (!response) {
|
||||
errorMessage(t('seriesCard.uploadError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move series from local-only to synced
|
||||
const uploadedSeries: SyncedSeries | undefined = localOnlySeries.find(
|
||||
(series: SyncedSeries): boolean => series.id === seriesId
|
||||
);
|
||||
setLocalOnlySeries((prevSeries: SyncedSeries[]): SyncedSeries[] => {
|
||||
return prevSeries.filter((series: SyncedSeries): boolean => series.id !== seriesId);
|
||||
});
|
||||
if (uploadedSeries) {
|
||||
setLocalSyncedSeries((prev: SyncedSeries[]): SyncedSeries[] => [...prev, uploadedSeries]);
|
||||
setServerSyncedSeries((prev: SyncedSeries[]): SyncedSeries[] => [...prev, uploadedSeries]);
|
||||
}
|
||||
return true;
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t('seriesCard.uploadError'));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a server-only series to the local database.
|
||||
* @param seriesId - The ID of the series to download
|
||||
* @returns True if download was successful, false otherwise
|
||||
*/
|
||||
async function download(seriesId: string): Promise<boolean> {
|
||||
if (isCurrentlyOffline()) return false;
|
||||
|
||||
try {
|
||||
const response: CompleteSeries = await System.authGetQueryToServer(
|
||||
'series/sync/download',
|
||||
session.accessToken,
|
||||
lang,
|
||||
{ seriesId }
|
||||
);
|
||||
|
||||
if (!response) {
|
||||
errorMessage(t('seriesCard.downloadError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const syncStatus: boolean = await window.electron.invoke<boolean>('db:series:syncSave', response);
|
||||
if (!syncStatus) {
|
||||
errorMessage(t('seriesCard.downloadError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move series from server-only to synced
|
||||
const downloadedSeries: SyncedSeries | undefined = serverOnlySeries.find(
|
||||
(series: SyncedSeries): boolean => series.id === seriesId
|
||||
);
|
||||
setServerOnlySeries((prevSeries: SyncedSeries[]): SyncedSeries[] => {
|
||||
return prevSeries.filter((series: SyncedSeries): boolean => series.id !== seriesId);
|
||||
});
|
||||
if (downloadedSeries) {
|
||||
setLocalSyncedSeries((prev: SyncedSeries[]): SyncedSeries[] => [...prev, downloadedSeries]);
|
||||
setServerSyncedSeries((prev: SyncedSeries[]): SyncedSeries[] => [...prev, downloadedSeries]);
|
||||
}
|
||||
return true;
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t('seriesCard.downloadError'));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs changes from server to local database for a specific series.
|
||||
* Only transfers entities that have changed based on the comparison.
|
||||
* @param seriesId - The ID of the series to sync
|
||||
* @returns True if sync was successful, false otherwise
|
||||
*/
|
||||
async function syncFromServer(seriesId: string): Promise<boolean> {
|
||||
if (isCurrentlyOffline()) return false;
|
||||
|
||||
try {
|
||||
const seriesToFetch: SeriesSyncCompare | undefined = seriesToSyncFromServer.find(
|
||||
(series: SeriesSyncCompare): boolean => series.id === seriesId
|
||||
);
|
||||
if (!seriesToFetch) {
|
||||
errorMessage(t('seriesCard.syncFromServerError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const response: CompleteSeries = await System.authPostToServer(
|
||||
'series/sync/server-to-client',
|
||||
{ seriesToSync: seriesToFetch },
|
||||
session.accessToken,
|
||||
lang
|
||||
);
|
||||
|
||||
if (!response) {
|
||||
errorMessage(t('seriesCard.syncFromServerError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const syncStatus: boolean = await window.electron.invoke<boolean>('db:series:sync:toClient', response);
|
||||
if (!syncStatus) {
|
||||
errorMessage(t('seriesCard.syncFromServerError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove from pending sync list
|
||||
setSeriesToSyncFromServer((prev: SeriesSyncCompare[]): SeriesSyncCompare[] =>
|
||||
prev.filter((series: SeriesSyncCompare): boolean => series.id !== seriesId)
|
||||
);
|
||||
return true;
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t('seriesCard.syncFromServerError'));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs local changes to the server for a specific series.
|
||||
* Only transfers entities that have changed based on the comparison.
|
||||
* @param seriesId - The ID of the series to sync
|
||||
* @returns True if sync was successful, false otherwise
|
||||
*/
|
||||
async function syncToServer(seriesId: string): Promise<boolean> {
|
||||
if (isCurrentlyOffline()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const seriesToFetch: SeriesSyncCompare | undefined = seriesToSyncToServer.find(
|
||||
(series: SeriesSyncCompare): boolean => series.id === seriesId
|
||||
);
|
||||
if (!seriesToFetch) {
|
||||
// La série n'est plus dans la liste - probablement déjà sync par AutoSyncOnReconnect
|
||||
// Retourner true car ce n'est pas une erreur, juste déjà fait
|
||||
return true;
|
||||
}
|
||||
|
||||
const seriesToSync: CompleteSeries = await window.electron.invoke<CompleteSeries>(
|
||||
'db:series:sync:toServer',
|
||||
seriesToFetch
|
||||
);
|
||||
if (!seriesToSync) {
|
||||
errorMessage(t('seriesCard.syncToServerError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const response: boolean = await System.authPatchToServer(
|
||||
'series/sync/client-to-server',
|
||||
{ series: seriesToSync },
|
||||
session.accessToken,
|
||||
lang
|
||||
);
|
||||
|
||||
if (!response) {
|
||||
errorMessage(t('seriesCard.syncToServerError'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove from pending sync list
|
||||
setSeriesToSyncToServer((prev: SeriesSyncCompare[]): SeriesSyncCompare[] =>
|
||||
prev.filter((series: SeriesSyncCompare): boolean => series.id !== seriesId)
|
||||
);
|
||||
return true;
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t('seriesCard.syncToServerError'));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Syncs all series that have local changes to the server.
|
||||
*/
|
||||
async function syncAllToServer(): Promise<void> {
|
||||
for (const diff of seriesToSyncToServer) {
|
||||
await syncToServer(diff.id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the sync status of all series by comparing local and server data.
|
||||
* Updates the context with the latest sync information.
|
||||
*/
|
||||
async function refreshSeries(): Promise<void> {
|
||||
try {
|
||||
let localSeriesResponse: SyncedSeries[] = [];
|
||||
let serverSeriesResponse: SyncedSeries[] = [];
|
||||
|
||||
if (!isCurrentlyOffline()) {
|
||||
if (offlineMode.isDatabaseInitialized) {
|
||||
localSeriesResponse = await window.electron.invoke<SyncedSeries[]>('db:series:synced');
|
||||
|
||||
// Get lastOnlineTimestamp from localStorage (or 0 if not set)
|
||||
const lastOnlineStr: string | null = localStorage.getItem('lastOnlineTimestamp');
|
||||
const lastOnlineTimestamp: number = lastOnlineStr ? parseInt(lastOnlineStr, 10) : 0;
|
||||
|
||||
// Get local tombstones since lastOnlineTimestamp via IPC
|
||||
const localTombstones: RemovedItemRecord[] = await window.electron.invoke<RemovedItemRecord[]>(
|
||||
'db:tombstones:since',
|
||||
lastOnlineTimestamp
|
||||
);
|
||||
|
||||
// Call server with POST and tombstones
|
||||
const serverResponse: SyncedSeriesResponse = await System.authPostToServer<SyncedSeriesResponse>(
|
||||
'series/synced',
|
||||
{ lastOnlineTimestamp, tombstones: localTombstones },
|
||||
session.accessToken,
|
||||
lang
|
||||
);
|
||||
|
||||
serverSeriesResponse = serverResponse.series;
|
||||
|
||||
// Apply server tombstones locally via IPC
|
||||
await window.electron.invoke<void>('db:tombstones:apply:series', serverResponse.tombstones);
|
||||
} else {
|
||||
// No local DB but online - just get server series without tombstones
|
||||
const serverResponse: SyncedSeriesResponse = await System.authPostToServer<SyncedSeriesResponse>(
|
||||
'series/synced',
|
||||
{ lastOnlineTimestamp: 0, tombstones: [] },
|
||||
session.accessToken,
|
||||
lang
|
||||
);
|
||||
serverSeriesResponse = serverResponse.series;
|
||||
}
|
||||
} else {
|
||||
if (offlineMode.isDatabaseInitialized) {
|
||||
localSeriesResponse = await window.electron.invoke<SyncedSeries[]>('db:series:synced');
|
||||
}
|
||||
}
|
||||
|
||||
setServerSyncedSeries(serverSeriesResponse);
|
||||
setLocalSyncedSeries(localSeriesResponse);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
errorMessage(e.message);
|
||||
} else {
|
||||
errorMessage(t('seriesCard.refreshError'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
upload,
|
||||
download,
|
||||
syncFromServer,
|
||||
syncToServer,
|
||||
syncAllToServer,
|
||||
refreshSeries,
|
||||
localOnlySeries,
|
||||
serverOnlySeries,
|
||||
seriesToSyncToServer,
|
||||
seriesToSyncFromServer
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user