# Plan — todo-app (간단한 To-Do 앱) > PDCA Phase: **Plan** · Feature: `todo-app` · 작성일: 2026-06-15 > 스택: Node 22 (ESM) + Express · Postgres(`src/db.js`) · MinIO/S3(`src/s3.js`) · podman 로컬 / Coolify 배포 ## Executive Summary | 관점 | 요약 | |------|------| | **Problem** | 할 일을 기록·관리할 화면과 저장소가 없다. 현재 앱은 연결 확인용 엔드포인트(`/db`, `/s3`)만 존재한다. | | **Solution** | Postgres에 할 일(todo)을 CRUD로 저장하고, 할 일별 첨부파일은 MinIO/S3에 저장하는 단일 공용 To-Do 웹 앱. 기존 `db.js`/`s3.js`/`config.js`를 그대로 확장한다. | | **Function UX Effect** | 브라우저에서 할 일 추가/완료/삭제, 파일 첨부·다운로드가 즉시 동작. REST API도 함께 제공해 curl/외부 연동 가능. | | **Core Value** | DB(구조화 데이터) + S3(바이너리 파일)를 함께 쓰는 가장 단순하고 실용적인 표준 패턴을 적은 코드로 제공. | ## Context Anchor | 항목 | 내용 | |------|------| | **WHY** | 사내 워크스페이스 샘플을 실제로 쓸 수 있는 최소 To-Do 앱으로 만들어 DB+S3 활용 패턴을 보여준다. | | **WHO** | 로그인 없이 접근하는 단일 사용자/소규모 팀(단일 공용 목록). | | **RISK** | 첨부파일 처리(업로드 크기/타입, presigned URL), schema 마이그레이션 누락, S3 키-DB 레코드 정합성. | | **SUCCESS** | 브라우저에서 할 일 CRUD + 첨부 업로드/다운로드가 동작하고 `npm run db:check`/`minio:check`가 통과. | | **SCOPE** | IN: todo CRUD, 첨부 1개/항목, 웹 UI, REST API. OUT: 인증/멀티유저, 마감일/태그/검색, 실시간 동기화. | --- ## 1. 배경 및 목표 기존 `src/server.js`는 `/healthz`, `/db`, `/s3`, `/` 만 제공한다. 여기에 To-Do 도메인을 얹는다. - **목표**: "DB와 S3를 이용한 간단한 To-Do 앱"을 최소 변경으로 구현. - **설계 원칙**: 외부 연결은 반드시 `src/config.js`/`src/db.js`/`src/s3.js`를 경유(CLAUDE.md 규약). 비밀값은 env에서만. - **데이터 분담**: 구조화 데이터(todo 본문/상태) → Postgres, 바이너리(첨부파일) → MinIO/S3, 둘을 키로 연결. ## 2. 요구사항 ### 2.1 기능 요구사항 (FR) | ID | 요구사항 | 우선순위 | |----|----------|:--------:| | FR-01 | 할 일 목록 조회 (최신순) | Must | | FR-02 | 할 일 추가 (제목, 선택적 첨부파일 1개) | Must | | FR-03 | 할 일 완료/미완료 토글 | Must | | FR-04 | 할 일 삭제 (연결된 S3 첨부도 함께 삭제) | Must | | FR-05 | 첨부파일 업로드 → MinIO/S3 저장, DB에 키 기록 | Must | | FR-06 | 첨부파일 다운로드 (presigned URL 또는 프록시 스트리밍) | Must | | FR-07 | 간단한 웹 UI(HTML) — 목록/추가/완료/삭제/첨부 | Must | | FR-08 | REST API(JSON) — 위 동작을 curl로도 수행 가능 | Should | ### 2.2 비기능 요구사항 (NFR) | ID | 요구사항 | |----|----------| | NFR-01 | ESM, Node 22+, 외부 연결은 config/db/s3 경유 (CLAUDE.md 규약 준수) | | NFR-02 | 비밀값 코드/커밋 금지 — 모두 `.project-env` env에서 주입 | | NFR-03 | 업로드 파일 크기 제한(예: 10MB) 및 기본 검증 | | NFR-04 | podman 빌드/실행 및 Coolify 동일 Dockerfile에서 동작 | | NFR-05 | DB schema는 앱 시작 시 멱등(idempotent) 생성 또는 마이그레이션 스크립트 제공 | ## 3. 범위 (Scope) - **In scope**: todo CRUD, 항목당 첨부파일 1개(S3), 서버 렌더 웹 UI, JSON REST API, schema 부트스트랩. - **Out of scope**: 인증/멀티유저, 마감일·태그·우선순위·검색, 다중 첨부, 실시간(WebSocket), 페이지네이션. ## 4. 데이터 모델 (초안) ``` table todo ( id uuid pk default gen_random_uuid(), title text not null, done boolean not null default false, attachment_key text null, -- S3 object key (없으면 null) attachment_name text null, -- 원본 파일명 attachment_type text null, -- content-type created_at timestamptz not null default now() ) ``` - 첨부 S3 키 규칙(초안): `todos/{todo_id}/{원본파일명}` (버킷 `coolify-user-data`). - 삭제 시 DB row 삭제 + S3 object 삭제(정합성). ## 5. API 설계 (초안) | Method | Path | 설명 | |--------|------|------| | GET | `/` | 웹 UI (할 일 목록 페이지) | | GET | `/api/todos` | 목록(JSON) | | POST | `/api/todos` | 추가 (multipart: title + 선택 file) | | PATCH | `/api/todos/:id` | done 토글 | | DELETE | `/api/todos/:id` | 삭제(+ S3 첨부 삭제) | | GET | `/api/todos/:id/attachment` | 첨부 다운로드(presigned redirect 또는 스트리밍) | ## 6. 구현 항목 (모듈 단위) | 모듈 | 작업 | 신규/수정 | |------|------|:---------:| | `src/schema.js` (또는 `scripts/migrate.js`) | todo 테이블 멱등 생성 | 신규 | | `src/todos.repo.js` | DB CRUD 쿼리 (pool 사용) | 신규 | | `src/attachments.js` | S3 업로드/삭제/presigned URL (`s3.js` 확장) | 신규 | | `src/routes.todos.js` | REST 라우트 핸들러 | 신규 | | `src/server.js` | 라우트/정적·multipart 미들웨어 연결 | 수정 | | `public/index.html` (또는 인라인 템플릿) | 간단한 웹 UI | 신규 | | `package.json` | `multer`(업로드), `@aws-sdk/s3-request-presigner` 의존성 추가 | 수정 | ## 7. 의존성 - 추가 예정: `multer`(multipart 업로드 파싱), `@aws-sdk/s3-request-presigner`(다운로드 URL). 추가 후 `npm install`로 lock 갱신. - 기존: `express`, `pg`, `@aws-sdk/client-s3` 재사용. ## 8. Success Criteria | SC | 기준 | 검증 방법 | |----|------|-----------| | SC-01 | 브라우저에서 할 일 추가/완료/삭제가 동작 | 수동 + L2 UI 테스트 | | SC-02 | 첨부파일 업로드 시 S3에 객체 생성, DB에 key 기록 | `/s3` 또는 MinIO 확인 + DB row | | SC-03 | 첨부 다운로드가 정상 동작 | 다운로드 후 파일 일치 확인 | | SC-04 | 할 일 삭제 시 S3 객체도 삭제 | 삭제 후 S3 키 부재 확인 | | SC-05 | `npm run db:check` / `npm run minio:check` 통과 | CLI 실행 | | SC-06 | `podman build` + `podman run --env-file .project-env`로 동일 동작 | 컨테이너 기동 확인 | ## 9. 리스크 및 대응 | 리스크 | 대응 | |--------|------| | `gen_random_uuid()` 미존재(pgcrypto) | `pgcrypto` 확장 활성 확인 또는 앱에서 uuid 생성 | | 업로드 대용량/악성 파일 | 크기 제한(10MB), content-type 화이트리스트 검토 | | DB-S3 정합성(삭제 누락) | 삭제는 S3 먼저 시도 후 DB 삭제, 실패 시 로깅 | | presigned URL 만료/내부 호스트 노출 | 짧은 만료 또는 서버 프록시 스트리밍 중 Design에서 택1 | ## 10. 다음 단계 `/pdca design todo-app` — 3가지 아키텍처 옵션(최소변경 / 클린 / 실용균형)을 비교하고 데이터 모델·API·다운로드 방식을 확정.