186 lines
4.4 KiB
JavaScript
186 lines
4.4 KiB
JavaScript
import { and, asc, eq, gte, lte } from 'drizzle-orm'
|
|
import { z } from 'zod'
|
|
import { db } from '../db/client.js'
|
|
import { plannerEntries } from '../db/schema.js'
|
|
import { findAuthenticatedUser } from '../lib/authSession.js'
|
|
|
|
const dateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
|
|
const plannerPayloadSchema = z.object({
|
|
payload: z.record(z.any()),
|
|
})
|
|
|
|
const plannerRangeQuerySchema = z.object({
|
|
from: dateSchema.optional(),
|
|
to: dateSchema.optional(),
|
|
})
|
|
|
|
async function requireAuthenticatedUser(request, reply) {
|
|
const user = await findAuthenticatedUser(request)
|
|
|
|
if (!user) {
|
|
reply.code(401).send({
|
|
message: '인증이 필요합니다.',
|
|
})
|
|
return null
|
|
}
|
|
|
|
return user
|
|
}
|
|
|
|
export async function registerPlannerRoutes(app) {
|
|
app.get('/api/planner', async (request, reply) => {
|
|
const user = await requireAuthenticatedUser(request, reply)
|
|
|
|
if (!user) {
|
|
return
|
|
}
|
|
|
|
const query = plannerRangeQuerySchema.safeParse(request.query ?? {})
|
|
|
|
if (!query.success) {
|
|
return reply.code(400).send({
|
|
message: '조회 범위가 올바르지 않습니다.',
|
|
issues: query.error.flatten(),
|
|
})
|
|
}
|
|
|
|
const filters = [eq(plannerEntries.userId, user.id)]
|
|
|
|
if (query.data.from) {
|
|
filters.push(gte(plannerEntries.entryDate, query.data.from))
|
|
}
|
|
|
|
if (query.data.to) {
|
|
filters.push(lte(plannerEntries.entryDate, query.data.to))
|
|
}
|
|
|
|
const entries = await db
|
|
.select({
|
|
entryDate: plannerEntries.entryDate,
|
|
payload: plannerEntries.payload,
|
|
createdAt: plannerEntries.createdAt,
|
|
updatedAt: plannerEntries.updatedAt,
|
|
})
|
|
.from(plannerEntries)
|
|
.where(and(...filters))
|
|
.orderBy(asc(plannerEntries.entryDate))
|
|
|
|
return {
|
|
entries,
|
|
}
|
|
})
|
|
|
|
app.get('/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 [entry] = await db
|
|
.select()
|
|
.from(plannerEntries)
|
|
.where(
|
|
and(
|
|
eq(plannerEntries.userId, user.id),
|
|
eq(plannerEntries.entryDate, dateResult.data),
|
|
),
|
|
)
|
|
.limit(1)
|
|
|
|
return {
|
|
entry: entry ?? null,
|
|
}
|
|
})
|
|
|
|
app.put('/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 payloadResult = plannerPayloadSchema.safeParse(request.body)
|
|
|
|
if (!payloadResult.success) {
|
|
return reply.code(400).send({
|
|
message: '플래너 저장 데이터가 올바르지 않습니다.',
|
|
issues: payloadResult.error.flatten(),
|
|
})
|
|
}
|
|
|
|
const now = new Date()
|
|
|
|
const [entry] = await db
|
|
.insert(plannerEntries)
|
|
.values({
|
|
userId: user.id,
|
|
entryDate: dateResult.data,
|
|
payload: payloadResult.data.payload,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: [plannerEntries.userId, plannerEntries.entryDate],
|
|
set: {
|
|
payload: payloadResult.data.payload,
|
|
updatedAt: now,
|
|
},
|
|
})
|
|
.returning()
|
|
|
|
return {
|
|
message: '플래너가 저장되었습니다.',
|
|
entry,
|
|
}
|
|
})
|
|
|
|
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,
|
|
}
|
|
})
|
|
}
|