Commit Graph

10 Commits

Author SHA1 Message Date
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 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 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 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 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 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 4d70b1428a first commit 2026-05-18 11:25:51 +09:00