- Backend: FastAPI + MongoDB + Redis (카테고리, 할일 CRUD, 파일 첨부, 검색, 대시보드) - Frontend: Next.js 15 + Tailwind + React Query + Zustand - 통합 TodoModal: 생성/수정 모달 통합, 탭 구조 (기본/태그와 첨부) - 간트차트: 카테고리별 할일 타임라인 시각화 - TodoCard: 제목/카테고리/우선순위/태그/첨부 한줄 표시 - Docker Compose 배포 (Frontend:3010, Backend:8010, MongoDB:27021, Redis:6391) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
107 lines
3.1 KiB
Python
107 lines
3.1 KiB
Python
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, Query, Response, status
|
|
|
|
from app.database import get_database, get_redis
|
|
from app.models.todo import (
|
|
TodoCreate,
|
|
TodoUpdate,
|
|
TodoResponse,
|
|
TodoListResponse,
|
|
BatchRequest,
|
|
BatchResponse,
|
|
ToggleResponse,
|
|
)
|
|
from app.services.todo_service import TodoService
|
|
|
|
router = APIRouter(prefix="/api/todos", tags=["todos"])
|
|
|
|
|
|
def _get_service(
|
|
db=Depends(get_database),
|
|
redis=Depends(get_redis),
|
|
) -> TodoService:
|
|
return TodoService(db, redis)
|
|
|
|
|
|
# 중요: /batch를 /{id} 보다 위에 등록
|
|
@router.post("/batch", response_model=BatchResponse)
|
|
async def batch_action(
|
|
data: BatchRequest,
|
|
service: TodoService = Depends(_get_service),
|
|
):
|
|
"""F-019~F-021: 일괄 작업 (완료/삭제/카테고리 변경)"""
|
|
return await service.batch_action(data)
|
|
|
|
|
|
@router.get("", response_model=TodoListResponse)
|
|
async def list_todos(
|
|
page: int = Query(1, ge=1, description="페이지 번호"),
|
|
limit: int = Query(20, ge=1, le=100, description="페이지당 항목 수"),
|
|
completed: Optional[bool] = Query(None, description="완료 상태 필터"),
|
|
category_id: Optional[str] = Query(None, description="카테고리 필터"),
|
|
priority: Optional[str] = Query(None, description="우선순위 필터 (high/medium/low)"),
|
|
tag: Optional[str] = Query(None, description="태그 필터"),
|
|
sort: str = Query("created_at", description="정렬 기준 (created_at/due_date/priority)"),
|
|
order: str = Query("desc", description="정렬 방향 (asc/desc)"),
|
|
service: TodoService = Depends(_get_service),
|
|
):
|
|
"""F-002: 할일 목록 조회 (필터/정렬/페이지네이션)"""
|
|
return await service.list_todos(
|
|
page=page,
|
|
limit=limit,
|
|
completed=completed,
|
|
category_id=category_id,
|
|
priority=priority,
|
|
tag=tag,
|
|
sort=sort,
|
|
order=order,
|
|
)
|
|
|
|
|
|
@router.post("", response_model=TodoResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_todo(
|
|
data: TodoCreate,
|
|
service: TodoService = Depends(_get_service),
|
|
):
|
|
"""F-001: 할일 생성"""
|
|
return await service.create_todo(data)
|
|
|
|
|
|
@router.get("/{todo_id}", response_model=TodoResponse)
|
|
async def get_todo(
|
|
todo_id: str,
|
|
service: TodoService = Depends(_get_service),
|
|
):
|
|
"""F-003: 할일 상세 조회"""
|
|
return await service.get_todo(todo_id)
|
|
|
|
|
|
@router.put("/{todo_id}", response_model=TodoResponse)
|
|
async def update_todo(
|
|
todo_id: str,
|
|
data: TodoUpdate,
|
|
service: TodoService = Depends(_get_service),
|
|
):
|
|
"""F-004: 할일 수정"""
|
|
return await service.update_todo(todo_id, data)
|
|
|
|
|
|
@router.delete("/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_todo(
|
|
todo_id: str,
|
|
service: TodoService = Depends(_get_service),
|
|
):
|
|
"""F-005: 할일 삭제"""
|
|
await service.delete_todo(todo_id)
|
|
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
|
@router.patch("/{todo_id}/toggle", response_model=ToggleResponse)
|
|
async def toggle_todo(
|
|
todo_id: str,
|
|
service: TodoService = Depends(_get_service),
|
|
):
|
|
"""F-006: 할일 완료 토글"""
|
|
return await service.toggle_todo(todo_id)
|