v0.1.19 - 사이드 네비게이션과 D-DAY 중복 방지 적용

This commit is contained in:
2026-04-22 10:19:23 +09:00
parent fe538fc88b
commit 9b788406ea
6 changed files with 204 additions and 45 deletions

View File

@@ -40,6 +40,39 @@ async function requireAuthenticatedUser(request, reply) {
return user
}
function hasGoalRangeOverlap(leftStart, leftEnd, rightStart, rightEnd) {
return leftStart <= rightEnd && leftEnd >= rightStart
}
async function validateGoalSchedule({
userId,
activeFrom,
activeUntil,
status,
excludeGoalId = null,
}) {
if (!activeFrom || !activeUntil || status !== 'active') {
return null
}
const existingGoals = await db
.select()
.from(goals)
.where(eq(goals.userId, userId))
return existingGoals.find((goal) => {
if (excludeGoalId && goal.id === excludeGoalId) {
return false
}
if (goal.status !== 'active' || !goal.activeFrom || !goal.activeUntil) {
return false
}
return hasGoalRangeOverlap(activeFrom, activeUntil, goal.activeFrom, goal.activeUntil)
}) ?? null
}
export async function registerGoalRoutes(app) {
app.get('/api/goals', async (request, reply) => {
const user = await requireAuthenticatedUser(request, reply)
@@ -92,6 +125,31 @@ export async function registerGoalRoutes(app) {
})
}
if ((payload.data.activeFrom && !payload.data.activeUntil) || (!payload.data.activeFrom && payload.data.activeUntil)) {
return reply.code(400).send({
message: '표시 시작일과 종료일은 함께 입력해 주세요.',
})
}
if (payload.data.activeFrom && payload.data.activeUntil && payload.data.activeFrom > payload.data.activeUntil) {
return reply.code(400).send({
message: '표시 종료일은 시작일보다 빠를 수 없습니다.',
})
}
const overlappedGoal = await validateGoalSchedule({
userId: user.id,
activeFrom: payload.data.activeFrom ?? null,
activeUntil: payload.data.activeUntil ?? null,
status: payload.data.status ?? 'active',
})
if (overlappedGoal) {
return reply.code(409).send({
message: `표시 기간이 "${overlappedGoal.title}" 목표와 겹칩니다. D-DAY 기간은 하나만 설정할 수 있습니다.`,
})
}
const now = new Date()
const [goal] = await db
@@ -154,6 +212,36 @@ export async function registerGoalRoutes(app) {
})
}
const nextActiveFrom = payload.data.activeFrom !== undefined ? payload.data.activeFrom : existingGoal.activeFrom
const nextActiveUntil = payload.data.activeUntil !== undefined ? payload.data.activeUntil : existingGoal.activeUntil
const nextStatus = payload.data.status ?? existingGoal.status
if ((nextActiveFrom && !nextActiveUntil) || (!nextActiveFrom && nextActiveUntil)) {
return reply.code(400).send({
message: '표시 시작일과 종료일은 함께 입력해 주세요.',
})
}
if (nextActiveFrom && nextActiveUntil && nextActiveFrom > nextActiveUntil) {
return reply.code(400).send({
message: '표시 종료일은 시작일보다 빠를 수 없습니다.',
})
}
const overlappedGoal = await validateGoalSchedule({
userId: user.id,
activeFrom: nextActiveFrom,
activeUntil: nextActiveUntil,
status: nextStatus,
excludeGoalId: existingGoal.id,
})
if (overlappedGoal) {
return reply.code(409).send({
message: `표시 기간이 "${overlappedGoal.title}" 목표와 겹칩니다. D-DAY 기간은 하나만 설정할 수 있습니다.`,
})
}
const nextValues = {
updatedAt: new Date(),
}