Refactor sync data module

Splits the large `src/utils/sync/data.ts` file into smaller, more manageable modules to improve code organization, readability, and maintainability.
This commit is contained in:
gpt-engineer-app[bot]
2025-03-21 12:42:18 +00:00
parent 66c7b5f132
commit 335848cca8
7 changed files with 374 additions and 152 deletions

View File

@@ -0,0 +1,38 @@
/**
* 데이터 백업 및 복원 관련 기능
*/
import { BackupData } from './types';
/**
* 현재 로컬 데이터를 백업
*/
export const backupLocalData = (): BackupData => {
return {
budgetData: localStorage.getItem('budgetData'),
categoryBudgets: localStorage.getItem('categoryBudgets'),
transactions: localStorage.getItem('transactions')
};
};
/**
* 백업 데이터를 로컬 스토리지에 복원
*/
export const restoreLocalData = (backup: BackupData): void => {
if (backup.budgetData) {
localStorage.setItem('budgetData', backup.budgetData);
}
if (backup.categoryBudgets) {
localStorage.setItem('categoryBudgets', backup.categoryBudgets);
}
if (backup.transactions) {
localStorage.setItem('transactions', backup.transactions);
}
// UI 업데이트 이벤트 발생
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
window.dispatchEvent(new Event('transactionUpdated'));
};

View File

@@ -0,0 +1,17 @@
/**
* 동기화 코어 모듈
*/
// 타입 정의 내보내기
export * from './types';
// 백업 및 복구 기능 내보내기
export * from './backup';
export * from './recovery';
// 상태 확인 기능 내보내기
export * from './status';
// 동기화 작업 내보내기
export * from './syncOperations';

View File

@@ -0,0 +1,18 @@
/**
* 동기화 과정에서 오류 발생 시 복구 기능
*/
import { BackupData } from './types';
import { restoreLocalData } from './backup';
/**
* 동기화 오류 시 데이터 복구
*/
export const recoverFromSyncError = (backup: BackupData, error: unknown): void => {
console.error('데이터 동기화 중 치명적 오류:', error);
// 백업 데이터 복원
restoreLocalData(backup);
console.log('백업 데이터 복원 완료');
};

View File

@@ -0,0 +1,91 @@
/**
* 서버 및 로컬 데이터 상태 확인 기능
*/
import { supabase } from '@/lib/supabase';
import { DataStatus, ServerDataStatus, LocalDataStatus } from './types';
/**
* 서버에 데이터가 있는지 확인
*/
export const checkServerDataStatus = async (userId: string): Promise<ServerDataStatus> => {
try {
// 서버에 예산 데이터 확인
const { data: budgetData } = await supabase
.from('budgets')
.select('count')
.eq('user_id', userId)
.single();
// 서버에 트랜잭션 데이터 확인
const { data: transactionsData } = await supabase
.from('transactions')
.select('count')
.eq('user_id', userId)
.single();
const hasBudgetData = (budgetData?.count || 0) > 0;
const hasTransactionData = (transactionsData?.count || 0) > 0;
return {
hasBudgetData,
hasTransactionData,
hasData: hasBudgetData || hasTransactionData
};
} catch (error) {
console.error('서버 데이터 상태 확인 오류:', error);
return {
hasBudgetData: false,
hasTransactionData: false,
hasData: false
};
}
};
/**
* 로컬 데이터가 있는지 확인
*/
export const checkLocalDataStatus = (): LocalDataStatus => {
try {
const budgetDataStr = localStorage.getItem('budgetData');
const transactionsStr = localStorage.getItem('transactions');
let hasBudgetData = false;
if (budgetDataStr) {
const budgetData = JSON.parse(budgetDataStr);
hasBudgetData = budgetData?.monthly?.targetAmount > 0;
}
let hasTransactionData = false;
if (transactionsStr) {
const transactions = JSON.parse(transactionsStr);
hasTransactionData = Array.isArray(transactions) && transactions.length > 0;
}
return {
hasBudgetData,
hasTransactionData,
hasData: hasBudgetData || hasTransactionData
};
} catch (error) {
console.error('로컬 데이터 상태 확인 오류:', error);
return {
hasBudgetData: false,
hasTransactionData: false,
hasData: false
};
}
};
/**
* 서버와 로컬 데이터 상태 모두 확인
*/
export const checkDataStatus = async (userId: string): Promise<DataStatus> => {
const serverStatus = await checkServerDataStatus(userId);
const localStatus = checkLocalDataStatus();
return {
server: serverStatus,
local: localStatus
};
};

View File

@@ -0,0 +1,106 @@
/**
* 주요 동기화 작업 처리 모듈
*/
import { uploadBudgets, downloadBudgets } from '../budget';
import { uploadTransactions, downloadTransactions } from '../transaction';
import { setLastSyncTime } from '../time';
import { SyncResult } from './types';
/**
* 데이터 업로드 수행
*/
export const performUpload = async (userId: string): Promise<{
budgetUpload: boolean;
transactionUpload: boolean;
}> => {
try {
console.log('데이터 업로드 시작');
// 예산 데이터 업로드
let budgetUpload = false;
try {
await uploadBudgets(userId);
budgetUpload = true;
console.log('예산 업로드 성공');
} catch (error) {
console.error('예산 업로드 실패:', error);
}
// 트랜잭션 데이터 업로드
let transactionUpload = false;
try {
await uploadTransactions(userId);
transactionUpload = true;
console.log('트랜잭션 업로드 성공');
} catch (error) {
console.error('트랜잭션 업로드 실패:', error);
}
return { budgetUpload, transactionUpload };
} catch (error) {
console.error('업로드 작업 실패:', error);
return { budgetUpload: false, transactionUpload: false };
}
};
/**
* 데이터 다운로드 수행
*/
export const performDownload = async (userId: string): Promise<{
budgetDownload: boolean;
transactionDownload: boolean;
}> => {
try {
console.log('데이터 다운로드 시작');
// 예산 데이터 다운로드
let budgetDownload = false;
try {
await downloadBudgets(userId);
budgetDownload = true;
console.log('예산 다운로드 성공');
} catch (error) {
console.error('예산 다운로드 실패:', error);
}
// 트랜잭션 데이터 다운로드
let transactionDownload = false;
try {
await downloadTransactions(userId);
transactionDownload = true;
console.log('트랜잭션 다운로드 성공');
} catch (error) {
console.error('트랜잭션 다운로드 실패:', error);
}
return { budgetDownload, transactionDownload };
} catch (error) {
console.error('다운로드 작업 실패:', error);
return { budgetDownload: false, transactionDownload: false };
}
};
/**
* 동기화 결과 객체 생성
*/
export const createSyncResult = (
uploadResult: { budgetUpload: boolean; transactionUpload: boolean },
downloadResult: { budgetDownload: boolean; transactionDownload: boolean }
): SyncResult => {
const uploadSuccess = uploadResult.budgetUpload || uploadResult.transactionUpload;
const downloadSuccess = downloadResult.budgetDownload || downloadResult.transactionDownload;
return {
success: uploadSuccess || downloadSuccess,
partial: (uploadSuccess || downloadSuccess) && !(uploadSuccess && downloadSuccess),
uploadSuccess,
downloadSuccess,
details: {
budgetUpload: uploadResult.budgetUpload,
budgetDownload: downloadResult.budgetDownload,
transactionUpload: uploadResult.transactionUpload,
transactionDownload: downloadResult.transactionDownload
}
};
};

View File

@@ -0,0 +1,45 @@
/**
* 동기화 관련 타입 정의
*/
// 동기화 결과 타입 정의
export interface SyncResult {
success: boolean;
partial: boolean;
uploadSuccess: boolean;
downloadSuccess: boolean;
details?: {
budgetUpload?: boolean;
budgetDownload?: boolean;
transactionUpload?: boolean;
transactionDownload?: boolean;
};
}
// 백업 데이터 타입 정의
export interface BackupData {
budgetData: string | null;
categoryBudgets: string | null;
transactions: string | null;
}
// 서버 데이터 상태 타입
export interface ServerDataStatus {
hasBudgetData: boolean;
hasTransactionData: boolean;
hasData: boolean;
}
// 로컬 데이터 상태 타입
export interface LocalDataStatus {
hasBudgetData: boolean;
hasTransactionData: boolean;
hasData: boolean;
}
// 데이터 상태 타입
export interface DataStatus {
server: ServerDataStatus;
local: LocalDataStatus;
}

View File

@@ -1,29 +1,28 @@
import { supabase } from '@/lib/supabase'; /**
import { uploadBudgets, downloadBudgets } from './budget'; * 데이터 동기화 메인 모듈
import { uploadTransactions, downloadTransactions } from './transaction'; */
import { isSyncEnabled } from './config';
import { setLastSyncTime } from './time'; import { setLastSyncTime } from './time';
import {
BackupData,
SyncResult,
backupLocalData,
recoverFromSyncError,
checkDataStatus,
performUpload,
performDownload,
createSyncResult
} from './core';
export interface SyncResult { /**
success: boolean; * 모든 데이터를 동기화합니다 (서버/로컬 상태에 따라 업로드/다운로드 순서 결정)
partial: boolean; */
uploadSuccess: boolean;
downloadSuccess: boolean;
details?: {
budgetUpload?: boolean;
budgetDownload?: boolean;
transactionUpload?: boolean;
transactionDownload?: boolean;
};
}
// 모든 데이터를 동기화합니다 (업로드 우선 수행)
export const syncAllData = async (userId: string): Promise<SyncResult> => { export const syncAllData = async (userId: string): Promise<SyncResult> => {
// 로컬 데이터 백업 // 로컬 데이터 백업
const backupBudgetData = localStorage.getItem('budgetData'); const backup: BackupData = backupLocalData();
const backupCategoryBudgets = localStorage.getItem('categoryBudgets');
const backupTransactions = localStorage.getItem('transactions');
// 기본 결과 객체 생성
const result: SyncResult = { const result: SyncResult = {
success: false, success: false,
partial: false, partial: false,
@@ -40,132 +39,55 @@ export const syncAllData = async (userId: string): Promise<SyncResult> => {
try { try {
console.log('데이터 동기화 시작 - 사용자 ID:', userId); console.log('데이터 동기화 시작 - 사용자 ID:', userId);
// 서버에 데이터가 있는지 먼저 확인 // 동기화 기능이 활성화되어 있는지 확인
const { data: budgetData } = await supabase if (!isSyncEnabled()) {
.from('budgets') console.log('동기화가 비활성화되어 있어 작업을 건너뜁니다.');
.select('count') return result;
.eq('user_id', userId) }
.single();
const { data: transactionsData } = await supabase
.from('transactions')
.select('count')
.eq('user_id', userId)
.single();
const serverHasBudgetData = (budgetData?.count || 0) > 0;
const serverHasTransactionData = (transactionsData?.count || 0) > 0;
const serverHasData = serverHasBudgetData || serverHasTransactionData;
// 로컬 데이터 존재 여부 확인
const localHasBudgetData = backupBudgetData && JSON.parse(backupBudgetData).monthly.targetAmount > 0;
const localHasTransactionData = backupTransactions && JSON.parse(backupTransactions).length > 0;
const localHasData = localHasBudgetData || localHasTransactionData;
// 서버와 로컬 데이터 상태 확인
const status = await checkDataStatus(userId);
console.log('데이터 존재 여부:', { console.log('데이터 존재 여부:', {
: { : {
예산: serverHasBudgetData, 예산: status.server.hasBudgetData,
거래: serverHasTransactionData 거래: status.server.hasTransactionData
}, },
: { : {
예산: localHasBudgetData, 예산: status.local.hasBudgetData,
거래: localHasTransactionData 거래: status.local.hasTransactionData
} }
}); });
// 서버에 데이터가 없고 로컬에 데이터가 있으면 업로드 먼저 // 동기화 전략 결정 - 서버에 데이터가 없고 로컬에 데이터가 있으면 업로드 먼저
if (!serverHasData && localHasData) { if (!status.server.hasData && status.local.hasData) {
console.log('서버에 데이터가 없고 로컬에 데이터가 있어 업로드 우선 실행'); console.log('서버에 데이터가 없고 로컬에 데이터가 있어 업로드 우선 실행');
try { // 업로드 수행
// 예산 데이터 업로드 const uploadResult = await performUpload(userId);
if (localHasBudgetData) { result.uploadSuccess = uploadResult.budgetUpload || uploadResult.transactionUpload;
await uploadBudgets(userId); result.details!.budgetUpload = uploadResult.budgetUpload;
result.details!.budgetUpload = true; result.details!.transactionUpload = uploadResult.transactionUpload;
console.log('예산 업로드 성공');
}
// 트랜잭션 데이터 업로드
if (localHasTransactionData) {
await uploadTransactions(userId);
result.details!.transactionUpload = true;
console.log('트랜잭션 업로드 성공');
}
// 업로드 성공 설정
result.uploadSuccess = true;
} catch (uploadError) {
console.error('데이터 업로드 실패:', uploadError);
result.uploadSuccess = false;
}
} }
// 서버에 데이터가 있으면 다운로드 먼저 (기존 로직) // 서버에 데이터가 있으면 다운로드 먼저
else if (serverHasData) { else if (status.server.hasData) {
console.log('서버에 데이터가 있어 다운로드 우선 실행'); console.log('서버에 데이터가 있어 다운로드 우선 실행');
try { // 다운로드 수행
// 예산 데이터 다운로드 const downloadResult = await performDownload(userId);
if (serverHasBudgetData) { result.downloadSuccess = downloadResult.budgetDownload || downloadResult.transactionDownload;
await downloadBudgets(userId); result.details!.budgetDownload = downloadResult.budgetDownload;
result.details!.budgetDownload = true; result.details!.transactionDownload = downloadResult.transactionDownload;
console.log('예산 다운로드 성공');
}
// 트랜잭션 데이터 다운로드
if (serverHasTransactionData) {
await downloadTransactions(userId);
result.details!.transactionDownload = true;
console.log('트랜잭션 다운로드 성공');
}
// 다운로드 성공 설정
result.downloadSuccess = true;
} catch (downloadError) {
console.error('데이터 다운로드 실패:', downloadError);
result.downloadSuccess = false;
// 다운로드 실패 시 로컬 데이터 복원
if (backupBudgetData) {
localStorage.setItem('budgetData', backupBudgetData);
}
if (backupCategoryBudgets) {
localStorage.setItem('categoryBudgets', backupCategoryBudgets);
}
if (backupTransactions) {
localStorage.setItem('transactions', backupTransactions);
}
// UI 업데이트
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
window.dispatchEvent(new Event('transactionUpdated'));
}
// 다운로드 후 로컬 데이터 있으면 추가 업로드 시도 // 다운로드 후 로컬 데이터 있으면 추가 업로드 시도
if (result.downloadSuccess && localHasData) { if (result.downloadSuccess && status.local.hasData) {
console.log('다운로드 후 로컬 데이터 업로드 시도'); console.log('다운로드 후 로컬 데이터 업로드 시도');
try { // 업로드 수행
// 예산 데이터 업로드 const uploadResult = await performUpload(userId);
if (localHasBudgetData) { result.uploadSuccess = uploadResult.budgetUpload || uploadResult.transactionUpload;
await uploadBudgets(userId); result.details!.budgetUpload = uploadResult.budgetUpload;
result.details!.budgetUpload = true; result.details!.transactionUpload = uploadResult.transactionUpload;
console.log('예산 업로드 성공');
}
// 트랜잭션 데이터 업로드
if (localHasTransactionData) {
await uploadTransactions(userId);
result.details!.transactionUpload = true;
console.log('트랜잭션 업로드 성공');
}
// 업로드 성공 설정
result.uploadSuccess = true;
} catch (uploadError) {
console.error('데이터 업로드 실패:', uploadError);
result.uploadSuccess = false;
}
} }
} }
// 양쪽 다 데이터가 없는 경우 // 양쪽 다 데이터가 없는 경우
@@ -193,34 +115,16 @@ export const syncAllData = async (userId: string): Promise<SyncResult> => {
console.log('데이터 동기화 결과:', result); console.log('데이터 동기화 결과:', result);
return result; return result;
} catch (error) { } catch (error) {
console.error('데이터 동기화 중 치명적 오류:', error); // 오류 발생 시 백업 데이터 복원
recoverFromSyncError(backup, error);
// 백업 데이터 복원
if (backupBudgetData) {
localStorage.setItem('budgetData', backupBudgetData);
}
if (backupCategoryBudgets) {
localStorage.setItem('categoryBudgets', backupCategoryBudgets);
}
if (backupTransactions) {
localStorage.setItem('transactions', backupTransactions);
}
// UI 업데이트
window.dispatchEvent(new Event('budgetDataUpdated'));
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
window.dispatchEvent(new Event('transactionUpdated'));
result.success = false;
result.partial = false;
result.uploadSuccess = false;
result.downloadSuccess = false;
return result; return result;
} }
}; };
// 서버에 대한 안전한 동기화 래퍼 /**
* 서버에 대한 안전한 동기화 래퍼
*/
export const trySyncAllData = async (userId: string): Promise<SyncResult> => { export const trySyncAllData = async (userId: string): Promise<SyncResult> => {
console.log('안전한 데이터 동기화 시도'); console.log('안전한 데이터 동기화 시도');
let attempts = 0; let attempts = 0;
@@ -248,3 +152,6 @@ export const trySyncAllData = async (userId: string): Promise<SyncResult> => {
return trySync(); return trySync();
}; };
// SyncResult 타입 다시 내보내기 (기존 가져오는 코드와의 호환성)
export type { SyncResult } from './core';