Skip to content

Commit ae636a3

Browse files
Merge pull request #67 from ShipFriend0516/feature/draft-on-cloud
2 parents d068262 + 150e330 commit ae636a3

File tree

14 files changed

+755
-21
lines changed

14 files changed

+755
-21
lines changed

.github/workflows/build-test.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,6 @@ jobs:
3636
- name: Lint check
3737
run: pnpm lint
3838

39-
# 빌드 테스트
40-
- name: Build test
41-
run: pnpm build
42-
env:
43-
DB_URI: ${{ secrets.DB_URI }}
44-
4539
# cd:
4640
# name: Continuous Deployment
4741
# needs: ci

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ public/rss.xml
4747
public/atom.xml
4848
public/feed.json
4949
.vscode/settings.json
50+
51+
*/agents

app/api/drafts/route.ts

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { getServerSession } from 'next-auth';
2+
import dbConnect from '@/app/lib/dbConnect';
3+
import CloudDraft from '@/app/models/CloudDraft';
4+
5+
// GET /api/drafts - 사용자의 클라우드 임시저장본 조회
6+
export async function GET(req: Request) {
7+
try {
8+
const session = await getServerSession();
9+
if (!session?.user?.email) {
10+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
11+
}
12+
13+
await dbConnect();
14+
15+
// 30일 이상 지난 임시저장본이 있다면 삭제
16+
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
17+
await CloudDraft.deleteMany({
18+
createdAt: { $lt: thirtyDaysAgo },
19+
});
20+
21+
// 사용자의 임시저장본 조회 (최신순, 최대 3개)
22+
const drafts = await CloudDraft.find({ userId: session.user.email })
23+
.sort({ createdAt: -1 })
24+
.limit(3)
25+
.lean();
26+
27+
return Response.json({ success: true, drafts }, { status: 200 });
28+
} catch (error) {
29+
console.error('Cloud draft fetch error:', error);
30+
return Response.json({ error: 'Failed to fetch drafts' }, { status: 500 });
31+
}
32+
}
33+
34+
// POST /api/drafts - 클라우드 임시저장본 생성 또는 업데이트
35+
export async function POST(req: Request) {
36+
try {
37+
const session = await getServerSession();
38+
if (!session?.user?.email) {
39+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
40+
}
41+
42+
await dbConnect();
43+
44+
const {
45+
draftId,
46+
title,
47+
subTitle,
48+
content,
49+
tags,
50+
imageUrls,
51+
seriesId,
52+
isPrivate,
53+
} = await req.json();
54+
55+
if (!draftId) {
56+
return Response.json({ error: 'draftId required' }, { status: 400 });
57+
}
58+
59+
// 최소 검증: title 또는 content 필수
60+
if (!title && !content) {
61+
return Response.json(
62+
{ error: 'Draft must have title or content' },
63+
{ status: 400 }
64+
);
65+
}
66+
67+
const userId = session.user.email;
68+
69+
// 기존 임시저장본 확인
70+
const existingDraft = await CloudDraft.findOne({ draftId, userId });
71+
72+
if (existingDraft) {
73+
// 기존 임시저장본 업데이트
74+
const updatedDraft = await CloudDraft.findOneAndUpdate(
75+
{ draftId, userId },
76+
{
77+
title,
78+
subTitle,
79+
content,
80+
tags,
81+
imageUrls,
82+
seriesId,
83+
isPrivate,
84+
},
85+
{ new: true, runValidators: true }
86+
);
87+
88+
return Response.json(
89+
{ success: true, draft: updatedDraft },
90+
{ status: 200 }
91+
);
92+
} else {
93+
// 새 임시저장본 생성
94+
// 3개 제한 확인
95+
const draftCount = await CloudDraft.countDocuments({ userId });
96+
97+
if (draftCount >= 3) {
98+
// 가장 오래된 임시저장본 삭제
99+
const oldestDraft = await CloudDraft.findOne({ userId })
100+
.sort({ createdAt: 1 })
101+
.lean();
102+
103+
if (oldestDraft && !Array.isArray(oldestDraft)) {
104+
await CloudDraft.deleteOne({ _id: oldestDraft._id });
105+
}
106+
}
107+
108+
// 새 임시저장본 생성
109+
const newDraft = await CloudDraft.create({
110+
draftId,
111+
userId,
112+
title,
113+
subTitle,
114+
content,
115+
tags,
116+
imageUrls,
117+
seriesId,
118+
isPrivate,
119+
});
120+
121+
return Response.json({ success: true, draft: newDraft }, { status: 201 });
122+
}
123+
} catch (error) {
124+
console.error('Cloud draft save error:', error);
125+
return Response.json({ error: 'Failed to save draft' }, { status: 500 });
126+
}
127+
}
128+
129+
// DELETE /api/drafts?draftId=xxx - 특정 클라우드 임시저장본 삭제
130+
export async function DELETE(req: Request) {
131+
try {
132+
const session = await getServerSession();
133+
if (!session?.user?.email) {
134+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
135+
}
136+
137+
await dbConnect();
138+
139+
const { searchParams } = new URL(req.url);
140+
const draftId = searchParams.get('draftId');
141+
142+
if (!draftId) {
143+
return Response.json({ error: 'draftId required' }, { status: 400 });
144+
}
145+
146+
const result = await CloudDraft.deleteOne({
147+
draftId,
148+
userId: session.user.email,
149+
});
150+
151+
if (result.deletedCount === 0) {
152+
return Response.json({ error: 'Draft not found' }, { status: 404 });
153+
}
154+
155+
return Response.json({ success: true }, { status: 200 });
156+
} catch (error) {
157+
console.error('Cloud draft delete error:', error);
158+
return Response.json({ error: 'Failed to delete draft' }, { status: 500 });
159+
}
160+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
interface AutoSyncToggleProps {
2+
enabled: boolean;
3+
onToggle: (enabled: boolean) => void;
4+
}
5+
6+
const AutoSyncToggle = ({ enabled, onToggle }: AutoSyncToggleProps) => {
7+
return (
8+
<label className="inline-flex items-center gap-2 cursor-pointer">
9+
<span className="text-sm font-medium text-default">
10+
자동 클라우드 저장 (3분)
11+
</span>
12+
<div className="relative">
13+
<input
14+
type="checkbox"
15+
checked={enabled}
16+
onChange={(e) => onToggle(e.target.checked)}
17+
className="sr-only peer"
18+
/>
19+
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
20+
</div>
21+
</label>
22+
);
23+
};
24+
25+
export default AutoSyncToggle;

0 commit comments

Comments
 (0)