Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20564ba34b |
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
- 프로젝트명: 10 Minute Planner 웹 UI
|
- 프로젝트명: 10 Minute Planner 웹 UI
|
||||||
- 기술 스택: Vue 3 + Vite + TailwindCSS + JavaScript
|
- 기술 스택: Vue 3 + Vite + TailwindCSS + JavaScript
|
||||||
- 현재 기준 버전: `v0.1.13`
|
- 현재 기준 버전: `v0.1.14`
|
||||||
- Git 원격 저장소: `https://git.sori.studio/zenn/planner.sori.studio.git`
|
- Git 원격 저장소: `https://git.sori.studio/zenn/planner.sori.studio.git`
|
||||||
|
|
||||||
## 기준 디자인
|
## 기준 디자인
|
||||||
@@ -121,6 +121,7 @@
|
|||||||
- 현재는 로컬 저장도 계속 유지하면서 서버 저장을 병행하는 과도기 구조다.
|
- 현재는 로컬 저장도 계속 유지하면서 서버 저장을 병행하는 과도기 구조다.
|
||||||
- 로그인 시 서버 플래너 데이터로 `plannerRecords`를 교체하고, 로그아웃 시에는 로컬 저장 기반 데이터로 다시 복귀하도록 정리했다.
|
- 로그인 시 서버 플래너 데이터로 `plannerRecords`를 교체하고, 로그아웃 시에는 로컬 저장 기반 데이터로 다시 복귀하도록 정리했다.
|
||||||
- 이로 인해 다른 사용자 로그인 시 이전 로컬 데이터가 서버 계정 데이터와 섞일 위험을 줄였다.
|
- 이로 인해 다른 사용자 로그인 시 이전 로컬 데이터가 서버 계정 데이터와 섞일 위험을 줄였다.
|
||||||
|
- 로그인 상태에서 특정 날짜의 플래너 내용을 완전히 비우면, 서버 저장 대신 해당 날짜 엔트리를 삭제하도록 정리했다.
|
||||||
- 이미지 저장 기능은 추후 `print-only` 또는 별도 export 전용 레이아웃을 기준으로 구현하면 화면/인쇄/공유 결과를 맞추기 쉽다.
|
- 이미지 저장 기능은 추후 `print-only` 또는 별도 export 전용 레이아웃을 기준으로 구현하면 화면/인쇄/공유 결과를 맞추기 쉽다.
|
||||||
- Docker Compose는 프론트엔드와 백엔드를 함께 올리는 기준으로 설계하되, NAS 환경에 맞는 볼륨과 재시작 정책도 함께 고려한다.
|
- Docker Compose는 프론트엔드와 백엔드를 함께 올리는 기준으로 설계하되, NAS 환경에 맞는 볼륨과 재시작 정책도 함께 고려한다.
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ten-minute-planner",
|
"name": "ten-minute-planner",
|
||||||
"version": "0.1.13",
|
"version": "0.1.14",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ten-minute-planner",
|
"name": "ten-minute-planner",
|
||||||
"version": "0.1.13",
|
"version": "0.1.14",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.13"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ten-minute-planner",
|
"name": "ten-minute-planner",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.13",
|
"version": "0.1.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
14
src/App.vue
14
src/App.vue
@@ -12,7 +12,7 @@ import {
|
|||||||
readAuthState,
|
readAuthState,
|
||||||
signup,
|
signup,
|
||||||
} from './lib/authClient'
|
} from './lib/authClient'
|
||||||
import { fetchPlannerEntries, savePlannerEntry } from './lib/plannerApi'
|
import { deletePlannerEntry, fetchPlannerEntries, savePlannerEntry } from './lib/plannerApi'
|
||||||
import {
|
import {
|
||||||
createInitialPlannerRecords,
|
createInitialPlannerRecords,
|
||||||
persistPlannerState,
|
persistPlannerState,
|
||||||
@@ -739,7 +739,7 @@ function schedulePlannerSync(recordKey) {
|
|||||||
try {
|
try {
|
||||||
const record = plannerRecords[recordKey]
|
const record = plannerRecords[recordKey]
|
||||||
|
|
||||||
if (!record || !hasPlannerContent(record)) {
|
if (!record) {
|
||||||
if (syncTimers.size === 0) {
|
if (syncTimers.size === 0) {
|
||||||
syncStatus.value = 'cloud'
|
syncStatus.value = 'cloud'
|
||||||
syncMessage.value = '클라우드 동기화 연결됨'
|
syncMessage.value = '클라우드 동기화 연결됨'
|
||||||
@@ -747,6 +747,16 @@ function schedulePlannerSync(recordKey) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasPlannerContent(record)) {
|
||||||
|
await deletePlannerEntry(authToken.value, recordKey)
|
||||||
|
|
||||||
|
if (syncTimers.size === 0) {
|
||||||
|
syncStatus.value = 'cloud'
|
||||||
|
syncMessage.value = '클라우드에서 삭제됨'
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await savePlannerEntry(authToken.value, recordKey, {
|
await savePlannerEntry(authToken.value, recordKey, {
|
||||||
...record,
|
...record,
|
||||||
tasks: record.tasks.map((task) => ({ ...task })),
|
tasks: record.tasks.map((task) => ({ ...task })),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user