diff --git a/HANDOFF.md b/HANDOFF.md index 84e10ac..a786457 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -4,7 +4,7 @@ - 프로젝트명: 10 Minute Planner 웹 UI - 기술 스택: Vue 3 + Vite + TailwindCSS + JavaScript -- 현재 기준 버전: `v0.1.13` +- 현재 기준 버전: `v0.1.14` - Git 원격 저장소: `https://git.sori.studio/zenn/planner.sori.studio.git` ## 기준 디자인 @@ -121,6 +121,7 @@ - 현재는 로컬 저장도 계속 유지하면서 서버 저장을 병행하는 과도기 구조다. - 로그인 시 서버 플래너 데이터로 `plannerRecords`를 교체하고, 로그아웃 시에는 로컬 저장 기반 데이터로 다시 복귀하도록 정리했다. - 이로 인해 다른 사용자 로그인 시 이전 로컬 데이터가 서버 계정 데이터와 섞일 위험을 줄였다. +- 로그인 상태에서 특정 날짜의 플래너 내용을 완전히 비우면, 서버 저장 대신 해당 날짜 엔트리를 삭제하도록 정리했다. - 이미지 저장 기능은 추후 `print-only` 또는 별도 export 전용 레이아웃을 기준으로 구현하면 화면/인쇄/공유 결과를 맞추기 쉽다. - Docker Compose는 프론트엔드와 백엔드를 함께 올리는 기준으로 설계하되, NAS 환경에 맞는 볼륨과 재시작 정책도 함께 고려한다. diff --git a/backend/src/routes/planner.js b/backend/src/routes/planner.js index fd99a00..a8171e1 100644 --- a/backend/src/routes/planner.js +++ b/backend/src/routes/planner.js @@ -162,4 +162,35 @@ export async function registerPlannerRoutes(app) { }, } }) + + app.delete('/api/planner/:entryDate', async (request, reply) => { + const user = await requireAuthenticatedUser(request, reply) + + if (!user) { + return + } + + const dateResult = dateSchema.safeParse(request.params.entryDate) + + if (!dateResult.success) { + return reply.code(400).send({ + message: '날짜 형식이 올바르지 않습니다.', + }) + } + + const deletedEntries = await db + .delete(plannerEntries) + .where( + and( + eq(plannerEntries.userId, user.id), + eq(plannerEntries.entryDate, dateResult.data), + ), + ) + .returning() + + return { + message: deletedEntries.length > 0 ? '플래너가 삭제되었습니다.' : '삭제할 플래너가 없습니다.', + deleted: deletedEntries.length > 0, + } + }) } diff --git a/package-lock.json b/package-lock.json index 4c16477..52c331a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ten-minute-planner", - "version": "0.1.13", + "version": "0.1.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ten-minute-planner", - "version": "0.1.13", + "version": "0.1.14", "dependencies": { "vue": "^3.5.13" }, diff --git a/package.json b/package.json index aa046c5..29db8cb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ten-minute-planner", "private": true, - "version": "0.1.13", + "version": "0.1.14", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.vue b/src/App.vue index 68e6187..9152c3f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -12,7 +12,7 @@ import { readAuthState, signup, } from './lib/authClient' -import { fetchPlannerEntries, savePlannerEntry } from './lib/plannerApi' +import { deletePlannerEntry, fetchPlannerEntries, savePlannerEntry } from './lib/plannerApi' import { createInitialPlannerRecords, persistPlannerState, @@ -739,7 +739,7 @@ function schedulePlannerSync(recordKey) { try { const record = plannerRecords[recordKey] - if (!record || !hasPlannerContent(record)) { + if (!record) { if (syncTimers.size === 0) { syncStatus.value = 'cloud' syncMessage.value = '클라우드 동기화 연결됨' @@ -747,6 +747,16 @@ function schedulePlannerSync(recordKey) { return } + if (!hasPlannerContent(record)) { + await deletePlannerEntry(authToken.value, recordKey) + + if (syncTimers.size === 0) { + syncStatus.value = 'cloud' + syncMessage.value = '클라우드에서 삭제됨' + } + return + } + await savePlannerEntry(authToken.value, recordKey, { ...record, tasks: record.tasks.map((task) => ({ ...task })), diff --git a/src/lib/plannerApi.js b/src/lib/plannerApi.js index 38a0524..1bee643 100644 --- a/src/lib/plannerApi.js +++ b/src/lib/plannerApi.js @@ -49,3 +49,10 @@ export async function savePlannerEntry(token, entryDate, payload) { }, }) } + +export async function deletePlannerEntry(token, entryDate) { + return request(`/api/planner/${entryDate}`, { + method: 'DELETE', + token, + }) +}