mirror of
https://github.com/nad4-su/meeting-minutes.git
synced 2026-08-12 22:33:25 +09:00
Phase 1 foundation: Prisma + /api/meetings CRUD (#1)
* feat(phase-1): Prisma 연동 + 회의록 CRUD API Phase 1 foundation (ROADMAP #1~#3): - src/lib/db.ts: Prisma 클라이언트 싱글톤 (dev hot-reload 대응) - Meeting 스키마 확장: attendees/tags (String[]), template/depth/ summaryMode/customPrompt (향후 참조 복원용), createdAt 인덱스 - 첫 마이그레이션 `20260421104750_init` 생성 - /api/meetings (GET 목록 + POST 생성) - q 파라미터로 제목/transcript/markdown 부분 검색 - limit (max 100), offset 페이지네이션 - attendees/tags 배열 타입 정규화 - /api/meetings/[id] (GET/PUT/DELETE) - PUT은 markdownMinutes/title/attendees/tags만 편집 허용 - 존재하지 않는 id에 대해 404 - docker-compose.yml: tools 프로필에 migrate 서비스 추가 (app 컨테이너 bloat 없이 마이그레이션 실행) - Prisma mock 기반 API 테스트 14개 신규 (총 71 tests) * docs: migrate 실행 단계 추가, ROADMAP의 #1/#2 완료 체크 * docs: PR 본문 드래프트 추가
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { NextRequest } from 'next/server'
|
||||
import { prisma } from '@/lib/db'
|
||||
|
||||
const DEFAULT_LIMIT = 20
|
||||
const MAX_LIMIT = 100
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const url = new URL(request.url)
|
||||
const q = url.searchParams.get('q')?.trim() ?? ''
|
||||
const limit = Math.min(
|
||||
MAX_LIMIT,
|
||||
Math.max(1, Number(url.searchParams.get('limit')) || DEFAULT_LIMIT),
|
||||
)
|
||||
const offset = Math.max(0, Number(url.searchParams.get('offset')) || 0)
|
||||
|
||||
const where = q
|
||||
? {
|
||||
OR: [
|
||||
{ title: { contains: q, mode: 'insensitive' as const } },
|
||||
{ rawTranscript: { contains: q, mode: 'insensitive' as const } },
|
||||
{ markdownMinutes: { contains: q, mode: 'insensitive' as const } },
|
||||
],
|
||||
}
|
||||
: undefined
|
||||
|
||||
const [meetings, total] = await Promise.all([
|
||||
prisma.meeting.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: limit,
|
||||
skip: offset,
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
tags: true,
|
||||
attendees: true,
|
||||
template: true,
|
||||
summaryMode: true,
|
||||
status: true,
|
||||
},
|
||||
}),
|
||||
prisma.meeting.count({ where }),
|
||||
])
|
||||
|
||||
return Response.json({
|
||||
meetings,
|
||||
total,
|
||||
limit,
|
||||
offset,
|
||||
})
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : '알 수 없는 오류'
|
||||
return Response.json(
|
||||
{ error: `회의록 목록 조회 실패: ${message}` },
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const {
|
||||
title,
|
||||
rawTranscript,
|
||||
markdownMinutes,
|
||||
summaryMode,
|
||||
template,
|
||||
depth,
|
||||
customPrompt,
|
||||
attendees,
|
||||
tags,
|
||||
} = body
|
||||
|
||||
if (!title || typeof title !== 'string' || title.trim().length === 0) {
|
||||
return Response.json(
|
||||
{ error: '제목은 필수입니다.' },
|
||||
{ status: 400 },
|
||||
)
|
||||
}
|
||||
|
||||
if (!markdownMinutes || typeof markdownMinutes !== 'string') {
|
||||
return Response.json(
|
||||
{ error: '회의록 본문은 필수입니다.' },
|
||||
{ status: 400 },
|
||||
)
|
||||
}
|
||||
|
||||
const meeting = await prisma.meeting.create({
|
||||
data: {
|
||||
title: title.trim(),
|
||||
rawTranscript: typeof rawTranscript === 'string' ? rawTranscript : null,
|
||||
markdownMinutes,
|
||||
summaryMode: typeof summaryMode === 'string' ? summaryMode : null,
|
||||
template: typeof template === 'string' ? template : null,
|
||||
depth: typeof depth === 'string' ? depth : null,
|
||||
customPrompt: typeof customPrompt === 'string' ? customPrompt : null,
|
||||
attendees: Array.isArray(attendees)
|
||||
? attendees.filter((a) => typeof a === 'string')
|
||||
: [],
|
||||
tags: Array.isArray(tags)
|
||||
? tags.filter((t) => typeof t === 'string')
|
||||
: [],
|
||||
status: 'COMPLETED',
|
||||
},
|
||||
})
|
||||
|
||||
return Response.json(meeting, { status: 201 })
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : '알 수 없는 오류'
|
||||
return Response.json(
|
||||
{ error: `회의록 생성 실패: ${message}` },
|
||||
{ status: 500 },
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user