v0.1.20 - PostgreSQL 전환 및 Docker Compose 초안 추가

This commit is contained in:
2026-04-22 10:48:24 +09:00
parent 9b788406ea
commit 8ff4c979fa
24 changed files with 614 additions and 248 deletions

View File

@@ -9,7 +9,6 @@ const goalSchema = z.object({
targetDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
activeFrom: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
activeUntil: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
status: z.enum(['active', 'done', 'archived']).optional(),
color: z.string().trim().min(4).max(32).optional(),
})
@@ -18,13 +17,11 @@ const goalUpdateSchema = z.object({
targetDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
activeFrom: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
activeUntil: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
status: z.enum(['active', 'done', 'archived']).optional(),
color: z.string().trim().min(4).max(32).optional(),
})
const goalQuerySchema = z.object({
query: z.string().trim().optional(),
status: z.enum(['active', 'done', 'archived', 'all']).optional(),
})
async function requireAuthenticatedUser(request, reply) {
@@ -48,10 +45,9 @@ async function validateGoalSchedule({
userId,
activeFrom,
activeUntil,
status,
excludeGoalId = null,
}) {
if (!activeFrom || !activeUntil || status !== 'active') {
if (!activeFrom || !activeUntil) {
return null
}
@@ -65,7 +61,7 @@ async function validateGoalSchedule({
return false
}
if (goal.status !== 'active' || !goal.activeFrom || !goal.activeUntil) {
if (!goal.activeFrom || !goal.activeUntil) {
return false
}
@@ -92,10 +88,6 @@ export async function registerGoalRoutes(app) {
const filters = [eq(goals.userId, user.id)]
if (query.data.status && query.data.status !== 'all') {
filters.push(eq(goals.status, query.data.status))
}
if (query.data.query) {
filters.push(like(goals.title, `%${query.data.query}%`))
}
@@ -141,7 +133,6 @@ export async function registerGoalRoutes(app) {
userId: user.id,
activeFrom: payload.data.activeFrom ?? null,
activeUntil: payload.data.activeUntil ?? null,
status: payload.data.status ?? 'active',
})
if (overlappedGoal) {
@@ -161,10 +152,8 @@ export async function registerGoalRoutes(app) {
activeFrom: payload.data.activeFrom ?? null,
activeUntil: payload.data.activeUntil ?? null,
color: payload.data.color ?? '#1c1917',
status: payload.data.status ?? 'active',
createdAt: now,
updatedAt: now,
completedAt: payload.data.status === 'done' ? now : null,
})
.returning()
@@ -214,8 +203,6 @@ 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: '표시 시작일과 종료일은 함께 입력해 주세요.',
@@ -232,7 +219,6 @@ export async function registerGoalRoutes(app) {
userId: user.id,
activeFrom: nextActiveFrom,
activeUntil: nextActiveUntil,
status: nextStatus,
excludeGoalId: existingGoal.id,
})
@@ -262,22 +248,10 @@ export async function registerGoalRoutes(app) {
nextValues.activeUntil = payload.data.activeUntil
}
if (payload.data.status !== undefined) {
nextValues.status = payload.data.status
}
if (payload.data.color !== undefined) {
nextValues.color = payload.data.color
}
if (payload.data.status === 'done' && !existingGoal.completedAt) {
nextValues.completedAt = new Date()
}
if (payload.data.status && payload.data.status !== 'done') {
nextValues.completedAt = null
}
const [goal] = await db
.update(goals)
.set(nextValues)
@@ -289,4 +263,42 @@ export async function registerGoalRoutes(app) {
goal,
}
})
app.delete('/api/goals/:goalId', async (request, reply) => {
const user = await requireAuthenticatedUser(request, reply)
if (!user) {
return
}
const params = z.object({
goalId: z.coerce.number().int().positive(),
}).safeParse(request.params)
if (!params.success) {
return reply.code(400).send({
message: '목표 식별자가 올바르지 않습니다.',
})
}
const [existingGoal] = await db
.select()
.from(goals)
.where(and(eq(goals.id, params.data.goalId), eq(goals.userId, user.id)))
.limit(1)
if (!existingGoal) {
return reply.code(404).send({
message: '목표를 찾을 수 없습니다.',
})
}
await db
.delete(goals)
.where(and(eq(goals.id, params.data.goalId), eq(goals.userId, user.id)))
return {
message: '목표가 삭제되었습니다.',
}
})
}