-
-
+
+
+
+
diff --git a/app/hooks/post/useAutoSync.ts b/app/hooks/post/useAutoSync.ts
new file mode 100644
index 0000000..cf09600
--- /dev/null
+++ b/app/hooks/post/useAutoSync.ts
@@ -0,0 +1,48 @@
+import { useEffect, useRef } from 'react';
+
+interface AutoSyncConfig {
+ enabled: boolean;
+ intervalMs: number;
+ onSync: () => Promise
;
+ deps: any[];
+}
+
+const useAutoSync = ({ enabled, intervalMs, onSync, deps }: AutoSyncConfig) => {
+ const intervalRef = useRef(null);
+ const lastSyncDataRef = useRef('');
+
+ useEffect(() => {
+ if (!enabled) {
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ intervalRef.current = null;
+ }
+ return;
+ }
+
+ intervalRef.current = setInterval(async () => {
+ const currentData = JSON.stringify(deps);
+
+ if (currentData !== lastSyncDataRef.current) {
+ try {
+ await onSync();
+ lastSyncDataRef.current = currentData;
+ console.log(
+ 'Auto-sync completed at',
+ new Date().toLocaleTimeString()
+ );
+ } catch (error) {
+ console.error('Auto-sync failed:', error);
+ }
+ }
+ }, intervalMs);
+
+ return () => {
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ }
+ };
+ }, [enabled, intervalMs, onSync, ...deps]);
+};
+
+export default useAutoSync;
diff --git a/app/hooks/post/useCloudDraft.ts b/app/hooks/post/useCloudDraft.ts
new file mode 100644
index 0000000..2de4462
--- /dev/null
+++ b/app/hooks/post/useCloudDraft.ts
@@ -0,0 +1,92 @@
+import axios from 'axios';
+import { useEffect, useRef, useState } from 'react';
+import { v4 as uuidv4 } from 'uuid';
+import { CloudDraft } from '@/app/types/Draft';
+
+const useCloudDraft = () => {
+ const [cloudDrafts, setCloudDrafts] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [autoSyncEnabled, setAutoSyncEnabled] = useState(false);
+ const draftIdRef = useRef(null);
+
+ useEffect(() => {
+ if (!draftIdRef.current) {
+ draftIdRef.current = uuidv4();
+ }
+ }, []);
+
+ useEffect(() => {
+ const savedSetting = localStorage.getItem('cloudDraftAutoSync');
+ setAutoSyncEnabled(savedSetting === 'true');
+ }, []);
+
+ const fetchCloudDrafts = async () => {
+ try {
+ setLoading(true);
+ const response = await axios.get('/api/drafts');
+ if (response.data.success) {
+ setCloudDrafts(response.data.drafts);
+ }
+ } catch (error) {
+ console.error('Failed to fetch cloud drafts:', error);
+ throw error;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const saveToCloud = async (draftData: {
+ title: string;
+ subTitle: string;
+ content: string;
+ tags: string[];
+ imageUrls: string[];
+ seriesId?: string;
+ isPrivate: boolean;
+ }) => {
+ if (!draftIdRef.current) {
+ throw new Error('Draft ID not initialized');
+ }
+
+ try {
+ const response = await axios.post('/api/drafts', {
+ draftId: draftIdRef.current,
+ ...draftData,
+ });
+ return response.data;
+ } catch (error) {
+ console.error('Failed to save draft to cloud:', error);
+ throw error;
+ }
+ };
+
+ const deleteCloudDraft = async (draftId: string) => {
+ try {
+ await axios.delete(`/api/drafts?draftId=${draftId}`);
+ setCloudDrafts((prev) => prev.filter((d) => d.draftId !== draftId));
+ } catch (error) {
+ console.error('Failed to delete cloud draft:', error);
+ throw error;
+ }
+ };
+
+ const toggleAutoSync = (enabled: boolean) => {
+ setAutoSyncEnabled(enabled);
+ localStorage.setItem('cloudDraftAutoSync', enabled.toString());
+ };
+
+ const getCurrentDraftId = () => draftIdRef.current;
+
+ return {
+ cloudDrafts,
+ loading,
+ autoSyncEnabled,
+ fetchCloudDrafts,
+ saveToCloud,
+ deleteCloudDraft,
+ toggleAutoSync,
+ getCurrentDraftId,
+ };
+};
+
+export default useCloudDraft;
diff --git a/app/models/CloudDraft.ts b/app/models/CloudDraft.ts
new file mode 100644
index 0000000..ae79faf
--- /dev/null
+++ b/app/models/CloudDraft.ts
@@ -0,0 +1,56 @@
+import { Schema, model, models } from 'mongoose';
+
+const cloudDraftSchema = new Schema(
+ {
+ draftId: {
+ type: String,
+ required: true,
+ unique: true,
+ index: true,
+ },
+ userId: {
+ type: String,
+ required: true,
+ index: true,
+ },
+ title: {
+ type: String,
+ default: '',
+ },
+ subTitle: {
+ type: String,
+ default: '',
+ },
+ content: {
+ type: String,
+ default: '',
+ },
+ tags: {
+ type: [String],
+ default: [],
+ },
+ imageUrls: {
+ type: [String],
+ default: [],
+ },
+ seriesId: {
+ type: String,
+ default: '',
+ },
+ isPrivate: {
+ type: Boolean,
+ default: false,
+ },
+ },
+ {
+ timestamps: true, // createdAt, updatedAt 자동 생성
+ }
+);
+
+// 효율적인 쿼리를 위한 복합 인덱스
+cloudDraftSchema.index({ userId: 1, createdAt: -1 });
+
+const CloudDraft =
+ models.CloudDraft || model('CloudDraft', cloudDraftSchema);
+
+export default CloudDraft;
diff --git a/app/types/Draft.ts b/app/types/Draft.ts
new file mode 100644
index 0000000..2adb01f
--- /dev/null
+++ b/app/types/Draft.ts
@@ -0,0 +1,31 @@
+export interface CloudDraft {
+ _id: string;
+ draftId: string;
+ userId: string;
+ title: string;
+ subTitle: string;
+ content: string;
+ tags: string[];
+ imageUrls: string[];
+ seriesId?: string;
+ isPrivate: boolean;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+export interface LocalDraft {
+ title: string;
+ subTitle: string;
+ content?: string;
+ tags: string[];
+ seriesId?: string;
+ isPrivate: boolean;
+}
+
+export interface DraftListItem {
+ id: string; // draftId for cloud, 'local' for local
+ title: string;
+ date: Date;
+ source: 'local' | 'cloud';
+ data: LocalDraft | CloudDraft;
+}
diff --git a/package.json b/package.json
index 7d905aa..11183d3 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"react-lottie-player": "^2.1.0",
"resend": "^6.5.2",
"sharp": "^0.33.5",
+ "uuid": "^13.0.0",
"zustand": "^5.0.3"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3ae0005..e5edb6d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -65,6 +65,9 @@ importers:
sharp:
specifier: ^0.33.5
version: 0.33.5
+ uuid:
+ specifier: ^13.0.0
+ version: 13.0.0
zustand:
specifier: ^5.0.3
version: 5.0.3(@types/react@18.3.20)(react@18.3.1)
@@ -3701,6 +3704,10 @@ packages:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
hasBin: true
+ uuid@13.0.0:
+ resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
+ hasBin: true
+
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@@ -8471,6 +8478,8 @@ snapshots:
uuid@10.0.0: {}
+ uuid@13.0.0: {}
+
uuid@8.3.2: {}
v8-compile-cache-lib@3.0.1: {}