템플릿이 나중에 반드시 필요해질 공용 기능을 미리 갖춘다.
multer 취약점을 해소한 김에, 안전한 업로드 기준도 함께 세운다.
- modules/upload — 단일 파일 업로드 엔드포인트(POST /api/files)와 재사용 가능한 검증 설정
- 확장자와 MIME 을 **둘 다** 검사하는 허용목록 방식.
확장자만 보면 위장이 쉽고, MIME 만 보면 클라이언트가 보낸 값이라 신뢰할 수 없다.
.html/.svg/.js 등 브라우저가 실행할 수 있는 형식은 허용 목록에서 제외.
- 저장 파일명은 서버가 UUID 로 생성한다. 클라이언트 파일명을 경로에 쓰면 경로 탈출이 가능하다.
- 크기 한도(UPLOAD_MAX_SIZE_MB, 기본 5MB) 초과 시 FILE_002(413).
- 도메인 에러 코드 FILE_001/FILE_002 추가 후 세 플랫폼 사전 동기화.
함께 고친 것:
- 전역 필터가 프레임워크의 영문 메시지를 그대로 노출하고 있었다.
크기 초과 시 multer 의 "File too large" 가 사용자에게 보였다 —
이제 코드에 대응하는 레지스트리 문구만 사용하며, 재정의는 BusinessException 으로만 가능하다.
- sync:error-codes 가 컨테이너 안에서 실행되면 형제 디렉터리를 찾지 못해
cryptic 한 mkdir 오류를 냈다. 원인과 해결 방법을 알리도록 보강.
- 업로드 볼륨 경로에 compose 기본값을 주어 기존 .env 에 키가 없어도 동작하게 함.
실제 multipart 요청으로 정상 업로드·형식 거부·MIME 위장·경로 탈출 시도·크기 초과·
파일 누락 시나리오를 모두 검증했다.
알려진 한계(확장자·MIME 검사는 파일 내용을 보지 않는다)는 security.md 에 명시하고,
업로드 디렉터리를 정적 서빙하지 말 것을 규칙으로 남겼다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 공통 위젯 위치를 lib/widgets/ 에서 규칙상 목표인 lib/shared/widgets/ 로 이동
- 기본 생성된 카운터 위젯 테스트가 실제 앱과 맞지 않아 실패 상태였다 —
앱 렌더링을 검증하는 테스트로 교체
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
지금까지 "커밋 전 lint/test 통과"는 문서상의 약속일 뿐 강제되지 않았다.
- CI(GitHub Actions) 추가 — 세 스택의 lint·test·build 와 backend e2e 를 PR 마다 실행.
error-codes-sync 잡은 sync 를 재실행한 뒤 git diff 로 생성 파일 드리프트를 검사한다.
레지스트리만 고치고 sync 를 빠뜨리면 클라이언트 사전이 조용히 어긋나기 때문.
- lint 스크립트에 --max-warnings 0 적용. 기존에는 경고가 있어도 통과해
"Warning 0" 규칙이 사실상 무의미했다. 자동 수정은 lint:fix 로 분리.
- 문서로만 있던 규칙을 린트로 이관:
any/dynamic 금지, console/print 금지, Vue 인라인 스타일·Options API 금지,
Dart const 누락·await 누락, HttpException 직접 생성 차단(BusinessException 유도).
- backend 에 @/ 경로 alias 설정 (tsconfig paths + jest moduleNameMapper).
공통 규칙이 요구하는 절대경로 alias 가 backend 에만 빠져 있었다.
- npm audit 취약점 10건 중 9건 해결.
남은 multer(high)는 platform-express v12 가 필요해 Nest v11 생태계와 충돌하고,
파일 업로드 엔드포인트가 없어 도달 경로가 없다 — security.md 에 재검토 조건 기록.
- PR 템플릿 추가.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
세 클라이언트가 공유하는 계약 중 정의가 빠져 있던 부분을 채운다.
- 목록 응답 형태를 {items, pageInfo} 로 고정하고 커서/오프셋 쿼리 DTO 제공
- @SkipTransform() 으로 스트림·204 등 래핑하면 안 되는 응답을 제외
- ApiStandardResponse/ApiErrorResponse 로 Swagger 문서를 실응답과 일치시킴
(컨트롤러는 raw 를 반환하지만 실제 응답은 래핑되어 문서가 어긋나 있었다)
- RequestIdMiddleware 로 모든 요청에 x-request-id 부여
- API 설계 규약(api-design.md) 신설 — REST 경로·쿼리·날짜/ID 표현·버전 관리
또한 .gitignore 의 data/ 패턴이 모든 depth 에 매칭되어
flutter/lib/features/user/data/ 의 소스가 커밋되지 않던 문제를 함께 고친다.
(user_provider.dart 가 import 하는 파일이 저장소에 없어 flutter analyze 가 깨져 있었다)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자에게 노출되는 실패가 에러 코드 없이 만들어질 수 없도록 구조를 바꾼다.
기존에는 예외 필터가 HTTP 상태로 코드를 추론해 도메인별 에러를 표현할 수 없었고,
세 플랫폼의 에러 사전을 손으로 맞춰야 해서 어긋나기 쉬웠다.
- 코드↔메시지↔HTTP 상태를 error-codes.ts 레지스트리 한 곳으로 통합
- BusinessException 은 에러 코드를 생성자 필수 인자로 받아 코드 누락을 타입으로 차단
- sync:error-codes 스크립트가 프론트·플러터 사전을 자동 생성 (수동 복사 제거)
- 429 전용 코드 REQ_001 추가 (기존에는 "시스템 오류"로 잘못 안내)
- AUTH_002(세션 만료)를 클라이언트에서 실제로 발행하도록 구현
- 응답에 traceId 를 실어 로그와 대조 가능하게 함
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- frontend: BaseButton 공통 컴포넌트, auth.store(세션 인증)·useAuth(로그인 흐름),
라우터 가드를 auth.store 와 연동
- flutter: dio_provider(인터셉터 적용 Dio DI), features/user 수직 슬라이스
(model·repository·provider) 예제
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>