Files
to-do/docs/02-design/features/todo-app.design.md
2026-06-15 15:11:50 +09:00

9.5 KiB

Design — todo-app (간단한 To-Do 앱)

PDCA Phase: Design · Feature: todo-app · 작성일: 2026-06-15 선택 아키텍처: Option C — 실용 균형 · 첨부 다운로드: 서버 프록시 스트리밍 상위 문서: Plan

Context Anchor

항목 내용
WHY 사내 워크스페이스 샘플을 실제로 쓸 수 있는 최소 To-Do 앱으로 만들어 DB+S3 활용 패턴을 보여준다.
WHO 로그인 없이 접근하는 단일 사용자/소규모 팀(단일 공용 목록).
RISK 첨부파일 처리(크기/타입), schema 마이그레이션 누락, S3 키-DB 레코드 정합성.
SUCCESS 브라우저에서 할 일 CRUD + 첨부 업로드/다운로드가 동작하고 npm run db:check/minio:check 통과.
SCOPE IN: todo CRUD, 첨부 1개/항목, 웹 UI, REST API. OUT: 인증/멀티유저, 마감일/태그/검색, 실시간.

1. Overview

기존 Express 앱(src/server.js)에 To-Do 도메인을 추가한다. 구조화 데이터는 Postgres(src/db.js pool), 첨부파일 바이너리는 MinIO/S3(src/s3.js client)에 저장하고 두 저장소를 S3 object key로 연결한다.

아키텍처 결정 (Option C — 실용 균형): route / repo / attachments 3개 모듈로 관심사를 분리하되, 별도 service 계층은 두지 않는다(라우트 핸들러가 repo+attachments를 조합). "간단한 앱" 목표에 맞는 최소한의 경계.

다운로드 결정 (서버 프록시 스트리밍): 브라우저는 앱 엔드포인트에만 요청하고, 서버가 GetObjectCommand로 S3 객체를 받아 응답 스트림으로 전달. 내부 MinIO 호스트(minio.bokdev.in) 노출 없이 환경 무관하게 동작. → presigned URL 불필요, @aws-sdk/s3-request-presigner 의존성 추가하지 않음.

2. 모듈 구조

src/
  config.js          (기존) 변경 없음
  db.js              (기존) pool, pingDb — 재사용
  s3.js              (기존) s3 client, pingS3 — 재사용. GetObject/PutObject/DeleteObject 추가 export
  schema.js          (신규) todo 테이블 멱등 생성 (앱 시작 시 ensureSchema 호출)
  todos.repo.js      (신규) DB CRUD 쿼리 (pool 사용)
  attachments.js     (신규) S3 put / get(stream) / delete 래퍼
  routes.todos.js    (신규) Express Router — REST 핸들러
  server.js          (수정) express.json/static 미들웨어, schema 부트스트랩, 라우터 마운트
public/
  index.html         (신규) 단일 페이지 웹 UI (vanilla JS fetch)

3. 데이터 모델

-- schema.js 에서 CREATE TABLE IF NOT EXISTS 로 멱등 실행
CREATE TABLE IF NOT EXISTS todo (
  id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  title            text NOT NULL,
  done             boolean NOT NULL DEFAULT false,
  attachment_key   text,          -- S3 object key (없으면 NULL)
  attachment_name  text,          -- 원본 파일명
  attachment_type  text,          -- content-type
  created_at       timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_todo_created_at ON todo (created_at DESC);
  • gen_random_uuid()는 pgcrypto 확장이 필요. 미존재 환경 대비: schema.js에서 CREATE EXTENSION IF NOT EXISTS pgcrypto를 먼저 시도하고, 권한 부족으로 실패하면 앱에서 crypto.randomUUID()로 id를 생성해 INSERT (fallback).
  • S3 키 규칙: todos/{todo_id}/{원본파일명} — 버킷은 config.s3.bucket(coolify-user-data).

4. API 계약

Method Path Request Response (성공) 비고
GET / text/html (index.html) 정적 제공
GET /api/todos 200 { data: Todo[] } created_at DESC
POST /api/todos multipart/form-data: title(필수), file(선택) 201 { data: Todo } file 있으면 S3 업로드 후 key 기록
PATCH /api/todos/:id application/json: { done: boolean } 200 { data: Todo } 토글
DELETE /api/todos/:id 204 S3 첨부 먼저 삭제 후 DB row 삭제
GET /api/todos/:id/attachment 200 스트림(Content-Type, Content-Disposition) 프록시 스트리밍, 첨부 없으면 404

공통 규약

  • 성공 바디: { data: ... }, 에러 바디: { error: string }.
  • 입력 검증: title 비어있으면 400 { error }. 잘못된 :id(uuid 형식 아님) → 400. 없는 todo → 404.
  • 업로드 제한: multer limits.fileSize = 10MB. 초과 시 413 { error }.

Todo 객체 형태

{
  "id": "uuid",
  "title": "string",
  "done": false,
  "attachment_name": "report.pdf | null",
  "attachment_type": "application/pdf | null",
  "hasAttachment": true,
  "created_at": "ISO8601"
}

attachment_key는 응답에서 제외(내부 키 비노출), 대신 hasAttachment boolean 노출.

5. 처리 흐름 (핵심 시퀀스)

POST /api/todos (첨부 포함)

  1. multer가 title + file(메모리 버퍼) 파싱 → title 검증
  2. id 생성(또는 INSERT 후 반환된 id) → S3 key todos/{id}/{filename} 결정
  3. attachments.put(key, buffer, contentType) → S3 업로드
  4. repo: INSERT (attachment_key/name/type 포함) → 201 반환
  5. 3단계 실패 시 DB INSERT 하지 않음(또는 보상 삭제) → 500

DELETE /api/todos/:id

  1. repo: SELECT attachment_key
  2. key 있으면 attachments.del(key) 먼저 시도(실패는 로깅하되 계속)
  3. repo: DELETE row → 204

GET /api/todos/:id/attachment

  1. repo: SELECT attachment_key/name/type → 없으면 404
  2. attachments.getStream(key)Content-Type, Content-Disposition: attachment; filename=... 세팅
  3. S3 Body 스트림을 res로 pipe

6. 에러 처리 & 정합성

상황 처리
S3 업로드 성공, DB INSERT 실패 업로드한 객체 보상 삭제(best-effort) 후 500
DB 삭제 전 S3 삭제 실패 경고 로깅 후 DB 삭제 진행(고아 객체는 허용, 데이터 무결성 우선)
pgcrypto 미존재 crypto.randomUUID() fallback
파일 크기 초과 multer 에러 → 413

7. 의존성 변경

패키지 용도 비고
multer multipart 업로드 파싱(메모리 스토리지) 신규 추가
@aws-sdk/client-s3 GetObjectCommand/PutObjectCommand/DeleteObjectCommand 기존, 명령만 추가 사용

presigned 방식 미채택 → @aws-sdk/s3-request-presigner 추가하지 않음 (Plan 대비 변경점). 추가 후 npm install로 lock 갱신.

8. Test Plan

L1 — API 엔드포인트 (서버 기동 시)

  • GET /api/todos → 200, { data: [] } 형태
  • POST /api/todos (title만) → 201, data.id 존재
  • POST /api/todos (title 누락) → 400
  • PATCH /api/todos/:id {done:true} → 200, data.done=true
  • DELETE /api/todos/:id → 204
  • GET /api/todos/:bad-uuid/attachment → 400/404

L2 — UI 액션 (수동 또는 Playwright)

  • 페이지 로드 → 목록 렌더
  • 입력+추가 → 목록에 새 항목, 입력창 비워짐
  • 체크박스 토글 → done 반영
  • 삭제 → 항목 사라짐
  • 파일 첨부 추가 → 다운로드 링크 노출, 클릭 시 파일 받아짐

L3 — E2E 시나리오

  • 첨부 포함 추가 → 다운로드 → 삭제까지 전체 흐름 무오류
  • 연결 점검: npm run db:check, npm run minio:check 통과 (SC-05)
  • 컨테이너: podman build + podman run --env-file .project-env 동작 (SC-06)

9. Success Criteria 매핑

SC Design 반영 위치
SC-01 (CRUD UI) §2 routes.todos.js + public/index.html, §4 API
SC-02 (업로드→S3+DB key) §5 POST 흐름, §3 데이터 모델
SC-03 (다운로드) §5 GET attachment, 프록시 스트리밍
SC-04 (삭제 시 S3도 삭제) §5 DELETE 흐름, §6 정합성
SC-05 (db/minio check) §8 L3
SC-06 (podman) §8 L3

10. 보안/규약 체크

  • 비밀값: 전부 env(config.js) 경유, 코드/커밋 금지 (CLAUDE.md 준수).
  • 외부 연결: db/s3/config 모듈만 경유.
  • 입력 검증: title 길이/공백, uuid 형식, 파일 크기·(선택)타입.
  • 내부 S3 키/호스트 비노출(프록시 + 응답에서 key 제외).

11. Implementation Guide

11.1 구현 순서

  1. src/schema.js — ensureSchema (pgcrypto 시도 + CREATE TABLE)
  2. src/s3.js 확장 — putObject/getObjectStream/deleteObject export
  3. src/attachments.js — 키 생성 규칙 + put/getStream/del
  4. src/todos.repo.js — list/create/setDone/getById/remove
  5. src/routes.todos.js — 6개 핸들러 + multer 미들웨어 + 검증
  6. src/server.js — express.json, static(public), ensureSchema 호출, 라우터 마운트
  7. public/index.html — fetch 기반 UI
  8. package.json — multer 추가, npm install

11.2 핵심 파일

  • src/routes.todos.js (가장 로직 밀집), src/attachments.js, public/index.html

11.3 Session Guide (Module Map)

모듈 키 범위 포함 파일 의존
module-1 (데이터 계층) schema + repo + s3/attachments schema.js, s3.js(확장), attachments.js, todos.repo.js 없음
module-2 (API 계층) 라우트 + 서버 연결 + 의존성 routes.todos.js, server.js, package.json module-1
module-3 (UI) 웹 프런트 public/index.html module-2

권장 세션 분할

  • 1세션에 전부 가능(소규모). 분할 시: --scope module-1--scope module-2--scope module-3.

12. 다음 단계

/pdca do todo-app (전체) 또는 /pdca do todo-app --scope module-1 (점진 구현).