Commit Graph

49 Commits

Author SHA1 Message Date
ttipo 3f703b10f4 ci(backend): 테스트 파일까지 타입 검사 추가
빌드(tsconfig.build.json)는 spec 을 제외하고 ts-jest 는 isolatedModules 로 타입을 보지 않아,
테스트 파일의 타입 오류가 어떤 검사에도 걸리지 않았다. 타입이 틀린 목·픽스처로도 테스트가 통과한다.

- backend 에 type-check 스크립트(tsc --noEmit) 추가 — src·test·scripts 를 모두 본다
- CI backend 잡에 Type check 단계 추가, 커밋 전 점검·역할 문서와 PR 템플릿에 반영
- 이 검사로 드러난 기존 계약 테스트(error-codes.spec.ts)의 타입 오류 수정 — 테스트 의미는 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:07:53 +09:00
ttipo 7897f2de20 fix: 템플릿 점검 결과 반영 — Node 24·이미지 위생·운영 노출 최소화
템플릿 점검에서 나온 항목을 한 번에 반영한다. 항목별 이유:

- Node 20 → 24 LTS (Dockerfile·CI): Node 20 은 2026-04-30 에 지원이 끝나 보안 패치가 없다
- backend·frontend .dockerignore 추가: `COPY . .` 가 호스트 node_modules(Windows 바이너리)·dist·
  .env*(시크릿)를 이미지에 넣고 있었다
- 업로드 디렉터리 쓰기 검사(fail-fast): Linux 에서 Docker 가 root 소유로 만든 바인드 마운트에
  비-root 앱이 쓰지 못해 첫 업로드에서야 SYS_001 로 터졌다. 기동 시 해결 명령을 안내한다
- DB 커넥션 풀 100 → 10 (DB_POOL_MAX): PostgreSQL 기본 max_connections(100)를 앱 하나가 다 잡았다
- Swagger 운영 기본 비활성 (SWAGGER_ENABLED): API 구조 전체가 공개 도메인에 노출됐다
- 헬스체크 운영 응답에서 오류 상세 제거: 드라이버 오류 문구에 내부 주소·DB 계정명이 담긴다.
  원인은 error 로그로 남긴다
- nginx: location 의 add_header 때문에 정적 자원에서 보안 헤더가 빠지던 문제와,
  index.html 이 캐시돼 배포 직후 빈 화면이 날 수 있던 문제를 고친다
- vite allowedHosts: true 제거 (DEV_ALLOWED_HOSTS): DNS rebinding 보호가 꺼져 있었다
- 전역 설정을 configureApp() 으로 분리해 main.ts 와 e2e 가 공유: e2e 가 설정을 복사해 재현해
  실제 설정과 어긋나도 통과할 수 있었다. PORT 도 빈 값을 방어하는 파서를 거친다
- 쓰이지 않던 WEBSOCKET_PORT 제거: 별도 포트는 같은 도메인 라우팅과 맞지 않는다
- 예제 useUser 가 메시지 대신 에러 코드를 보관하도록 수정, testing.md 표 깨짐 수정,
  settings.json 허용 목록에서 npm install 제거 (Windows lock 파일 규칙과 충돌)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:06:02 +09:00
ttipo 6dcbb45eb9 refactor: 프론트·API 를 같은 도메인(/api)으로 통합
브라우저가 프론트와 API 를 서로 다른 도메인으로 호출하던 구조를 같은 도메인의 /api 경로로 통합한다.
운영은 Nginx Proxy Manager 의 Proxy Host(/ → frontend:8080)와 Custom location(/api → backend:3000)이,
개발은 vite dev 프록시가 같은 규칙으로 백엔드에 넘긴다.

- 같은 출처 호출이라 CORS 설정이 필요 없고 CSP connect-src 'self' 가 그대로 동작한다
  (기존 예시대로 별도 도메인에 배포하면 CSP 에 막혀 API 호출이 실패했다)
- 프론트 번들의 baseURL 이 /api 상대경로라 도메인이 바뀌어도 다시 빌드할 필요가 없다
- NPM 설정 절차와 주의점(Cache Assets 끄기, proxy 네트워크 생성)을 README "운영 배포"에 정리했다
- 프록시 설정이 빠지면 frontend nginx 가 /api 를 404 로 막는다 — index.html(200)로 조용히 실패하지 않게

함께 고친 CORS 문제:
- CORS_ORIGIN 에 콤마로 여러 오리진을 적으면 문자열 전체를 하나로 비교해 모두 막히던 버그
- 운영에서도 localhost:5173·localhost:3000 이 credentials 포함으로 항상 허용되던 문제
- 형식이 잘못된 오리진(경로 포함 등)은 기동 시점에 실패하도록 검증 추가

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 00:04:03 +09:00
ttipo 2f94b6db12 docs(rules): 에이전트 팀원 역할 규칙 추가
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
지금까지 .claude 에는 "무엇을 지키는가"(rules)와 "어떻게 만드는가"(skills)만 있었고
"누가 어디까지 책임지는가"를 적을 자리가 없었다.

역할 파일은 규칙을 복사하지 않고 참조만 한다. 구현 규칙을 옮겨 적으면 스킬과
진실 공급원이 둘로 갈라져, 한쪽만 고쳐졌을 때 어느 쪽이 맞는지 알 수 없게 된다.
역할 고유의 것(담당 범위·경계·완료 기준·인계 형식)만 담는다.

리뷰어와 페르소나 둘은 frontmatter 에서 Edit/Write 를 빼 도구 수준으로 읽기 전용이다.
CLAUDE.md 에는 @ import 하지 않는다 — 호출 시점에 로드되는 역할 정의다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 11:44:56 +09:00
ttipo 52e671cedb fix(backend): 운영 공개 URL 에 로컬 주소가 남으면 기동을 실패시킴
.env.production.example 이 개발용 값(localhost)을 그대로 담고 있어, 복사해 배포하면
서버는 정상 기동하지만 CORS 화이트리스트가 실제 도메인을 막고 프론트 번들은
localhost 를 호출한다 — 브라우저에서만 조용히 실패해 원인을 찾기 어렵다.

- CORS_ORIGIN·BACKEND_API_URL 에 로컬 주소나 예시 도메인이 있으면 운영에서 기동 실패
- 예제 값을 도메인 형태로 교체 (그대로 두면 이 검사에 걸린다)
- BACKEND_API_URL 은 앱이 쓰지 않지만 검사를 위해 compose 가 backend 에 주입한다
- 로컬에서 운영 이미지를 검증할 때만 ALLOW_LOCALHOST_URL=true 로 건너뛴다

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 11:42:23 +09:00
ttipo 94357d26bd perf(docker): 개발 프론트엔드를 컨테이너에서 호스트 vite 로 전환
Windows 바인드 마운트(./frontend:/app) 너머의 소스를 vite 가 모듈마다 읽는 데다
폴링 감시까지 항상 켜져 있어 첫 서빙에 수 분이 걸렸다. 호스트에서 띄우면 2초다.

- dev 오버라이드의 frontend 를 compose profile `frontend` 뒤로 숨긴다 (운영은 그대로)
- 폴링 감시는 CHOKIDAR_USEPOLLING 으로 컨테이너에서만 켠다
- dev 서버 포트·프록시 대상을 루트 .env.{mode} 에서 읽는다 (포트 SSOT 유지)
  포트 충돌 시 조용히 옮기지 않도록 strictPort — CORS_ORIGIN·포트 매핑이 묶여 있다

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 11:41:19 +09:00
ttipo 40af8a2e09 fix(ci): 모든 잡에 timeout-minutes 추가 — 멈춘 잡의 러너 점유 방지
CI (Flutter) / Flutter (analyze · test) (pull_request) Failing after 16m51s
CI / Backend (lint · test · e2e · build) (pull_request) Successful in 11m15s
CI / Frontend (lint · type-check · test) (pull_request) Successful in 9m12s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 10m51s
CI (Flutter) / Flutter (analyze · test) (push) Failing after 16m24s
CI / Backend (lint · test · e2e · build) (push) Failing after 21s
CI / Frontend (lint · type-check · test) (push) Failing after 14m24s
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
러너 환경에서 잡이 간헐적으로 멈추는 것을 확인했다.
Flutter 잡이 CPU 0%·네트워크 정지 상태로 25분 넘게 러너를 점유해
이후 잡들이 시작조차 하지 못했다. Frontend 잡에서도 같은 증상이 나타났다.

러너가 하나뿐이면 멈춘 잡 하나가 이후 전부를 굶긴다.
워크플로를 분리해도 점유 자체는 막지 못하므로 상한이 필요하다.

- backend 20분 / frontend 15분 / error-codes-sync 10분 / flutter 25분
- 증상과 즉시 해제 방법(잡 컨테이너 중지)을 ci/README.md 에 정리

CI 작성 시 넣었어야 할 기본 안전장치를 빠뜨렸던 것을 보완한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:48:31 +09:00
ttipo 962e060236 ci: Flutter 검사를 별도 워크플로로 분리 — 기본 비활성
CI (Flutter) / Flutter (analyze · test) (push) Failing after 21m26s
CI / Backend (lint · test · e2e · build) (push) Successful in 10m31s
CI / Frontend (lint · type-check · test) (push) Successful in 10m18s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 8m21s
Flutter 잡은 SDK 다운로드로 매 실행 수 분이 걸리는데,
backend/frontend 만 고친 커밋에서는 검사할 것이 없다.
러너가 잡을 하나씩 처리하므로 다른 검사까지 같이 느려졌다.

ci-flutter.yml 로 분리하고 flutter/ 경로가 바뀐 푸시·PR 에서만 실행되게 한다.
실제로 모바일 앱을 쓰기 시작하면 별도 설정 없이 자동으로 동작하고,
그 전까지는 CI 시간을 차지하지 않는다. workflow_dispatch 로 수동 실행도 가능하다.

Redis 를 compose profile 로 기본 비활성화한 것과 같은 방향이다 —
기반은 갖춰두되 쓰지 않을 때는 자원을 차지하지 않게 한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:11:06 +09:00
ttipo 1c0176640e fix(ci): 서비스 컨테이너 포트 매핑 제거 — 잡이 시작조차 못 하던 문제
CI / Backend (lint · test · e2e · build) (push) Successful in 10m30s
CI / Frontend (lint · type-check · test) (push) Successful in 9m52s
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
CI / Flutter (analyze · test) (push) Has been cancelled
러너를 붙이고 첫 실행에서 backend 잡의 모든 스텝이 checkout 부터 failure 로 표시됐다.
스텝이 실패한 게 아니라 서비스 컨테이너가 뜨지 못해 잡 준비 단계에서 죽은 것이다.

원인은 postgres 서비스의 ports: 5432:5432 였다.
개발 DB(project-db)가 이미 호스트 5432 를 점유하고 있어 바인딩이 충돌했다.

Gitea 러너는 잡을 컨테이너로 실행하고 서비스와 같은 네트워크에 붙이므로
호스트 포트 매핑이 애초에 필요 없고, 접근도 localhost 가 아니라 서비스 이름으로 해야 한다.
(GitHub Actions 는 잡이 VM 위에서 직접 돌아 localhost + ports 를 쓰는 점이 다르다)

- 서비스의 ports 매핑 제거
- e2e 스텝의 DB_HOST 를 localhost → postgres(서비스 이름)로 변경
- 두 플랫폼의 차이와 "모든 스텝이 한꺼번에 실패" 증상을 ci/README.md 에 정리

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 22:45:26 +09:00
ttipo ea46fe7817 ci: Gitea Actions 러너 설정 추가
CI / Backend (lint · test · e2e · build) (push) Failing after 1m10s
CI / Frontend (lint · type-check · test) (push) Successful in 10m58s
CI / Flutter (analyze · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
CI 워크플로는 저장소에 있지만 실행 주체(러너)가 없어 동작하지 않는 상태였다.
러너가 없으면 워크플로는 "Waiting for a runner" 로 대기만 한다.

- ci/docker-compose.runner.yml — act_runner 를 별도 스택으로 분리.
  앱 스택은 개발할 때만 띄우지만 러너는 항상 켜져 있어야 하므로 생명주기가 다르다.
- ci/.env.example — 등록 토큰은 ci/.env 에만 두며 커밋되지 않는다.
- ci/README.md — 설치 절차, 증상별 문제 해결, 잡별 리소스 특성.
- 라벨 기본값이 워크플로의 runs-on(ubuntu-latest)과 일치하는지 교차 확인했다.
  불일치는 잡이 영원히 대기하는 가장 흔한 원인이다.

러너가 도커 소켓에 접근해 사실상 호스트 관리자 권한을 갖는다는 점도 문서에 명시했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 22:23:37 +09:00
ttipo 20754edf26 fix: 회귀 점검에서 드러난 계약 테스트 실패와 compose 경고 수정
CI / Flutter (analyze · test) (push) Has been cancelled
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
FILE_001/FILE_002 를 추가하면서 클라이언트 계약 테스트의 기대 목록을 갱신하지 않아
frontend·flutter 테스트가 깨져 있었다. 테스트가 제 역할을 한 셈이지만,
전수 목록 비교라는 설계 자체가 문제였다.

세 플랫폼의 사전은 backend 레지스트리에서 생성되므로 목록 일치는 구조적으로 보장되고
CI 가 드리프트를 검사한다. 전수 비교는 도메인 코드를 추가할 때마다 무의미하게 깨질 뿐이다.
검증 대상을 "클라이언트 로직이 실제로 참조하는 코드의 존재"와 네이밍 규약으로 바꾼다.
(flutter 에는 누락돼 있던 네이밍 규약 검사도 추가)

또한 기존 .env 에 없는 키들(DB_SYNCHRONIZE, THROTTLE_*)로 compose 가 매번 경고를
출력하고 있었다. 코드 기본값과 동일한 값을 compose 레벨 기본값으로 명시해 해소한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 22:00:11 +09:00
ttipo aba274e56f feat(backend): 파일 업로드 공통 골격 추가 (로컬 디스크)
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / Flutter (analyze · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
템플릿이 나중에 반드시 필요해질 공용 기능을 미리 갖춘다.
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>
2026-09-17 21:47:10 +09:00
ttipo 750cd6362a fix(backend): multer 취약점 해소 — overrides 로 2.4.0 고정
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / Flutter (analyze · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
@nestjs/platform-express@11 이 multer 를 2.2.0 으로 정확 고정하는데,
이 버전에는 깊게 중첩된 필드명으로 자원을 소모시키는 DoS 취약점(high)이 있다.
상위 패키지가 버전을 박아두어 npm audit fix 로는 해결되지 않았다.

platform-express@12 가 쓰는 multer 버전을 확인하니 2.4.0 이었다.
즉 v12 로 올려서 얻는 결과가 multer 상향이므로, overrides 로 하위 패키지만 올려
Nest 생태계(v11) 업그레이드 없이 동일한 결과를 얻는다.

- npm audit: 10건 → 0건
- 같은 메이저(2.x) 안에서의 상향이라 API 호환 위험이 낮고,
  lint·단위 31개·e2e 7개·빌드·런타임 기동·Docker 빌드까지 확인했다.
- 이 패턴(전이 의존성 취약점을 overrides 로 끊기)과 주의사항을 security.md 에 규칙으로 정리.
  platform-express 를 v12 로 올릴 때 override 를 제거해야 한다는 조건도 함께 기록.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 21:11:39 +09:00
ttipo 32eb4fafca fix(frontend): dev 서버 API 프록시 추가 — 프론트가 백엔드를 호출하지 못하던 문제
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / Flutter (analyze · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
useApi 의 baseURL 이 '/api' 상대경로인데 vite dev 서버에 프록시가 없어,
브라우저의 API 요청이 백엔드에 닿지 않고 SPA 폴백으로 index.html 을 받고 있었다.

더 나쁜 것은 조용히 실패한다는 점이다 — HTTP 200 에 Content-Type: text/html 이라
인터셉터의 success 판별을 통과하지 못하고 HTML 문자열이 그대로 반환된다.
템플릿의 화면이 아직 API 를 호출하지 않아 드러나지 않았을 뿐이다.

- vite.config.ts 에 /api 프록시 추가. 대상은 VITE_DEV_API_PROXY 로 조정 가능하며
  docker 개발 환경에서는 compose 가 http://backend:3000 을 주입한다.
- DB_HOST 기본값을 'db' 에서 'localhost' 로 변경.
  compose 는 항상 DB_HOST=db 를 주입하므로 이 기본값은 호스트에서 직접 실행할 때만
  쓰인다 — 'db' 는 호스트에서 해석되지 않아 로컬 실행이 불가능했다.
- README 의 "Docker 없이 로컬 개발" 절차가 위 두 문제로 동작하지 않았다. 실제로 되는 절차로 교체.

프록시 경유 요청이 JSON 을 반환하고 SPA 라우팅도 유지되는 것을 확인했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 20:28:14 +09:00
ttipo e4262b2816 refactor(docker): Redis 를 기본 비활성으로 전환 (Compose profile)
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / Flutter (analyze · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
Redis 컨테이너가 항상 떠 있었지만 애플리케이션은 사용하지 않았다.
쓰지 않는 컨테이너는 메모리를 차지하고 "이게 왜 떠 있지" 하는 혼란을 준다.

- redis 서비스를 profiles: ["redis"] 로 숨겨 기본 기동에서 제외
- backend 의 redis 의존성을 required: false 로 두어 없어도 정상 기동
- 루트 .env 의 COMPOSE_PROFILES 한 줄로 켤 수 있게 함
  (데이터 볼륨과 REDIS_* 는 유지되어 켜면 이전 상태가 그대로 이어진다)

profile 미지정 시 서비스 목록에서 제외되는 것, redis 없이 앱이 정상 동작하는 것,
COMPOSE_PROFILES=redis 로 다시 기동되는 것을 모두 확인했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 18:48:02 +09:00
ttipo a217d13483 feat(backend): DB 마이그레이션 워크플로 추가
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / Flutter (analyze · test) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
엔티티를 추가하면 개발에서는 동작하지만 운영 배포 시 테이블이 생성되지 않는
구조적 결함이 있었다. app.module 은 운영에서 migrationsRun 을 켜두었지만
마이그레이션을 만들 수단(DataSource·CLI 스크립트·디렉터리)이 전혀 없었고,
규칙 문서도 "운영에서는 마이그레이션이 강제된다"는 원칙만 있고 절차가 없었다.

- CLI 전용 DataSource(src/data-source.ts)와 migration:* 스크립트 추가.
  ts-node 에 tsconfig-paths 를 등록해 엔티티가 @/ alias 를 써도 동작한다.
- DB 접속 설정을 database.config.ts 로 모아 앱과 CLI 가 공유하게 함.
  두 곳에 나눠 적으면 반드시 어긋난다.
- synchronize 기본값을 false 로 변경 (DB_SYNCHRONIZE=true 로 개발 한정 opt-in).
  켜져 있으면 개발 DB 가 항상 최신이라 migration:generate 가 "변경 없음"만 내놓아
  마이그레이션을 만들 수 없다 — 이것이 위 결함의 직접 원인이었다.
  운영에서는 이 값과 무관하게 항상 비활성화된다.
- 엔티티 추가 절차와 주의사항(생성 SQL 검토, 커밋 후 수정 금지)을 문서화하고
  커밋 전 체크리스트에 스키마 변경 항목 추가.

generate → run → revert 전 과정을 임시 엔티티로 검증했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 18:34:16 +09:00
ttipo 700a100a38 docs: 규칙 문서를 실제 구현에 맞춰 갱신
CI / Frontend (lint · type-check · test) (push) Has been cancelled
CI / Flutter (analyze · test) (push) Has been cancelled
CI / Backend (lint · test · e2e · build) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled
규칙과 구현이 어긋나 있던 부분을 바로잡고, 작업하며 발견한 함정을 기록한다.

- 스킬 문서의 "기본 제공 골격" 표를 실제 파일 목록과 일치시킴
- 커밋 전 체크리스트에 테스트 실행·에러 코드 동기화 절차 추가
- 커밋 규칙에 scope·브랜치 전략·PR 규칙 보강
- 새 규칙 파일(api-design, logging)을 CLAUDE.md import 목록에 등록하고,
  규칙 추가 시 등록이 필수라는 메타 규칙 명시
- README 를 현재 구조에 맞게 갱신 (traceId·페이지네이션·에러 코드 SSOT·로컬 개발 명령)

작업 중 실제로 겪은 함정들을 규칙으로 남긴다:
- Windows 에서 npm install 하면 Linux 전용 optional 의존성이 lock 에서 잘려
  Docker 의 npm ci 가 실패한다 → 의존성 변경은 컨테이너 안에서 수행
- --build 만으로는 node_modules 익명 볼륨이 갱신되지 않아
  새 패키지가 컨테이너에 반영되지 않는다 → --renew-anon-volumes 필요
- compose 는 미정의 변수를 빈 문자열로 주입해 Number('')===0 함정이 생긴다
- dev 는 PID 1 이 npm 이라 graceful shutdown 확인은 운영 이미지로 해야 한다
- Redis 는 컨테이너만 떠 있고 사용처가 없다 — 확장 지점임을 명시

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:32:23 +09:00
ttipo 258e0e37cc fix(flutter): 디렉터리 구조를 규칙에 맞추고 깨진 테스트 교체
- 공통 위젯 위치를 lib/widgets/ 에서 규칙상 목표인 lib/shared/widgets/ 로 이동
- 기본 생성된 카운터 위젯 테스트가 실제 앱과 맞지 않아 실패 상태였다 —
  앱 렌더링을 검증하는 테스트로 교체

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:31:46 +09:00
ttipo dac1603b03 ci: 규칙을 자동으로 강제하는 검사 추가
지금까지 "커밋 전 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>
2026-09-17 14:31:34 +09:00
ttipo e7c00e6f65 fix: 보안 규칙을 어기던 컨테이너·네트워크 설정 수정
security.md 가 금지한다고 적어둔 것을 실제 설정이 어기고 있었다.

- 프론트엔드 운영 컨테이너가 root 로 nginx 를 실행하고 있었다.
  nginx-unprivileged 로 전환하고 포트를 8080 으로 맞춤(비-root 는 1024 미만 바인드 불가).
- dev 오버라이드가 사용하는 빌드 스테이지에도 USER node 적용.
  익명 볼륨이 node 소유로 생성되도록 chown 을 USER 전환 앞에 둔다
  (그렇지 않으면 vite 가 node_modules/.vite 캐시를 쓰지 못한다).
- dev 의 DB/Redis 포트가 0.0.0.0 에 바인딩되어 같은 LAN 에 노출돼 있었다 → 127.0.0.1 로 한정.
- CSP 헤더 추가. 대신 현대 브라우저에서 권장되지 않는 X-XSS-Protection 은 제거.
- CORS 차단 시 Error 를 던져 500(SYS_001)이 되던 것을 헤더 미부여 방식으로 변경.
- 헬스체크가 localhost 를 ::1 로 해석해 항상 실패하던 문제 수정(127.0.0.1 명시).
- graceful shutdown 을 위해 init(tini)과 stop_grace_period 적용.
- *.example 의 실제같은 비밀번호를 placeholder 로 교체하고,
  비어 있던 서비스 env 예시에 안내를 채움.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:31:04 +09:00
ttipo bf102d758f feat: 기동·종료·헬스체크 견고성 보강
운영에서 조용히 문제를 일으킬 수 있는 지점들을 정리한다.

- env 검증(fail-fast): 설정이 잘못되면 서버가 뜨지 않는다.
  운영에서 DB_* 누락과 'change-me' 같은 예시 값 배포를 차단하고,
  문제를 하나씩이 아니라 한 번에 모아 보여준다.
  DB_PASSWORD 가 없을 때 'password' 로 조용히 동작하던 폴백도 제거.
- graceful shutdown: SIGTERM 시 요청·커넥션을 정리한 뒤 종료한다.
  종료 로그는 OnApplicationShutdown 훅으로 남긴다 —
  process.on(signal) 로 직접 잡으면 Nest 의 종료 흐름에 간섭해 종료 코드가 달라진다.
- 헬스체크 두 가지 결함 수정:
  pingCheck 기본 타임아웃 1초가 짧아 유휴 커넥션 재연결 시 정상 DB 를 down 으로 오판했고,
  실패 응답이 SYS_001 로 덮여 어느 구성요소가 죽었는지 알 수 없었다.
  모니터링이 읽는 엔드포인트이므로 표준 래퍼·에러 코드에서 제외한다.
- Rate limiting 을 @nestjs/throttler 로 일원화 (express-rate-limit 제거).
  두 리미터가 겹쳐 헤더의 한도와 실제 동작이 어긋나 있었다.
- e2e 를 응답 계약 검증으로 교체. 기존 테스트는 없는 경로를 검사해 이미 실패 상태였다.
- 로깅 규칙(logging.md) 신설 — 레벨 기준·민감정보 마스킹·traceId

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:30:29 +09:00
ttipo c5d165275c feat: 응답 계약 정의와 페이지네이션·추적 ID 추가
세 클라이언트가 공유하는 계약 중 정의가 빠져 있던 부분을 채운다.

- 목록 응답 형태를 {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>
2026-09-17 14:30:07 +09:00
ttipo f7ea60b8e7 feat: 에러 코드 SSOT 체계 도입
사용자에게 노출되는 실패가 에러 코드 없이 만들어질 수 없도록 구조를 바꾼다.

기존에는 예외 필터가 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>
2026-09-17 14:29:15 +09:00
ttipo 3b397b7788 fix: 기존 코드의 타입·린트 정합성 보강 (lint·type-check 통과)
- backend: HttpExceptionFilter status 를 HttpStatus 로 타입 지정(enum 비교),
  TransformInterceptor 를 Observable<T> 로, main.ts expressApp 캐스팅·bootstrap void 처리
- frontend: useApi 의 ErrorCodeLexicon 폴백 타입 보강(noUncheckedIndexedAccess)
- eslint --fix 포맷 정리(user 컨트롤러/서비스, 페이지)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:58:02 +09:00
ttipo b35ddfc9c3 feat: 공통 컴포넌트·인증·Riverpod 기초 골격 추가
- 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>
2026-06-28 13:58:01 +09:00
ttipo 6306160dda refactor: .claude 규칙 체계를 skills·rules 구조로 재편
- 슬래시 커맨드(nestjs/vue3/flutter)를 정식 스킬(.claude/skills)로 전환하고
  docker 스킬을 신설(루트 command.md 이관)
- 공통 규칙을 .claude/rules 로 모듈화(coding-common·response-format·error-codes·
  git-convention·security·env-structure·testing·checklist)하고 CLAUDE.md 에서 @import
- 파일·식별자 네이밍 규약, 테스트 규칙 추가
- .claude/settings.json(권한·환경) 추가
- CLAUDE.md·README.md 참조 갱신, 단독 실행(non-Docker) 안내 정리

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:58:01 +09:00
ttipo 49fef58c5f chore: 줄바꿈·에디터 설정 추가 (.gitattributes, .editorconfig)
저장소 줄바꿈을 LF로 정규화하고 에디터 공통 포맷(UTF-8, 2-space)을 정의해
OS·에디터에 따른 불필요한 diff를 방지한다.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 13:58:01 +09:00
ttipo c5896fbdb1 command.md 업데이트 2026-06-22 12:34:10 +09:00
ttipo 5cf52d3beb .env.production.example 업데이트 2026-06-22 12:33:32 +09:00
ttipo 6e0c745f5c docker-compose.dev.yml 업데이트 2026-06-17 18:14:27 +09:00
ttipo 5ffceb3923 fix: swc watch 모드용 chokidar 의존성 추가
@swc/cli가 chokidar를 optional peerDependency로 선언해 자동 설치되지
않아, watch 모드(start:dev)에서 'Cannot find module chokidar'로 실패하던
문제 수정. chokidar를 명시적 devDependency로 추가하고 lockfile 재생성.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 14:30:29 +09:00
ttipo 5d0eb9bd59 fix: backend package-lock.json을 올바른 lockfile로 재생성
package.json을 복사한 가짜 lockfile이라 npm ci가 실패하던 문제 수정.
clean install로 lockfileVersion 3 의존성 트리를 생성해 동기화.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:58:11 +09:00
ttipo 1be0ed0f28 backend/package-lock.json 업데이트 2026-06-16 13:35:32 +09:00
ttipo bc2ff5ea51 backend/package-lock.json 업데이트 2026-06-16 13:35:22 +09:00
ttipo 758a26d8f8 frontend/package-lock.json 추가 2026-06-16 11:48:53 +09:00
ttipo ff7be15948 backend/package-lock.json 업데이트 2026-06-16 11:48:24 +09:00
ttipo d9eba74e95 backend/package-lock.josn 추가 2026-06-16 11:48:17 +09:00
ttipo 6e88c7a21a backend/package-lock.json 삭제 2026-06-16 11:41:21 +09:00
ttipo 745a1d139a frontend/package-lock.json 삭제 2026-06-16 11:41:13 +09:00
ttipo 215477d797 frontend/.env.production.example 업데이트 2026-06-16 11:08:11 +09:00
ttipo fe9d489642 frontend/.env.development.example 업데이트 2026-06-16 11:01:00 +09:00
ttipo 87b104b570 backend/.env.production.example 업데이트 2026-06-16 11:00:47 +09:00
ttipo 37b7d870a9 backend/.env.development.example 업데이트 2026-06-16 11:00:37 +09:00
ttipo 749b5cc342 .env.production.example 업데이트 2026-06-16 11:00:22 +09:00
ttipo c873d17cba .env.development.example 업데이트 2026-06-16 11:00:09 +09:00
ttipo f5a18a52f4 docs: 템플릿 규칙 정비 및 환경변수 예제·README 추가
- CLAUDE.md: 모노레포 개요·표준 응답 포맷·에러 코드 맵(구현 일치)·환경변수 위임 구조(SSOT) 정리
- nestjs/vue3/flutter 규칙을 실제 구현(클래스명, env키, 디렉터리)과 정합화
- 루트 .env 단일 진실 공급원 구조에 맞춰 docker-compose 변수명 정정(BACKEND_API_URL, DB/REDIS_DATA_DIR)
- backend/frontend/flutter 예제 env 추가(루트 위임 반영, 서비스 고유 값만 작성)
- README.md 신규 작성, command.md 범용화, .gitignore에 *.example 재포함 규칙 추가

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:14:42 +09:00
ttipo 543d7b85fa .gitignore 업데이트 2026-06-09 16:06:40 +09:00
ttipo c2e970f453 Docker compose 구조 수정 2026-05-28 13:55:30 +09:00
ttipo 4d70b1428a first commit 2026-05-18 11:25:51 +09:00