尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證중구: 중고차 구매가이드 — 차종·연식별 고질병과 매장 점검 가이드
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗차종과 연식을 넣으면 중고차를 살 때 확인할 고질병과 체크 포인트를 알려주는 서비스.
학습 프로젝트입니다. 프론트엔드 개발자가 백엔드(NestJS·Prisma·PostgreSQL)·인증·테스트·배포까지 새 스택을 직접 익히려고 만들고 있습니다. 무엇을 새로 익혔는지, 폴더를 왜 이렇게 나눴는지는 학습 노트에 정리했습니다. 기획은 plan.md, 회고는 retrospective.md.
api/ NestJS + Prisma + PostgreSQL · web/ Next.js + Tailwind (포트 3201) · e2e/ Playwright| 기능 | 누가 | 이유 |
|---|---|---|
| 차종·연식별 고질병, 구매 시 확인법 | DB (사람이 검증) | 돈이 걸린 사실 정보라 AI의 그럴듯한 거짓말(환각)을 피해야 함 |
| 아무것도 모를 때 "무엇부터 알아볼지" | AI | 정답이 하나가 아닌 안내·설명 |
| 용도·예산별 차종 추천 | AI → DB 연결 | 추천 차종에 DB 고질병 링크를 붙여 줌 |
고질병 데이터는 초안(DRAFT)으로 들어가고, 검토 요청 → 관리자 승인(검증됨) 또는 반려를 거칩니다. 승인은 출처 URL이 있어야만 됩니다(서버 규칙). 아래 "고질병 심사 흐름" 참고. 샘플 데이터(세대 이름·엔진·미션·고질병·점검 포인트)는 전부 Claude가 쓴 초안이라 사실 확인이 필요합니다.
준비물: Node 22+, Docker Desktop. 패키지 매니저는 pnpm만 씁니다 (npm·yarn으로 설치하면 안내 메시지와 함께 멈춰요).
npm install -g pnpm # 처음 한 번만 (또는 brew install pnpm)
pnpm install # 저장소 맨 위 폴더에서. api·web을 한 번에 설치하고, .env가 없으면 .env.example을 복사해 둡니다
pnpm db:setup # 처음 한 번: PostgreSQL 컨테이너(포트 5434) + 테이블 생성 + 샘플 데이터 (⚠️ 기존 데이터는 지워짐)
pnpm dev # API http://localhost:3200 (문서 /docs) + 화면 http://localhost:3201 동시 실행
corepack enable로 pnpm을 받는 방법도 있지만, Node 22.13 이하의 corepack은 서명 키가 오래돼 Cannot find matching keyid 오류로 pnpm이 실행되지 않습니다. 그럴 땐 corepack disable 후 위처럼 npm으로 설치하세요.pnpm --dir api db:up → pnpm dev만 하면 됩니다.pnpm --dir api dev / pnpm --dir web dev.car-check)를 열면 위쪽 실행 목록에 전체 실행 (api + web), api, web, DB 처음 준비 (데이터 초기화) 가 나옵니다 (.run/ 폴더).package.json의 packageManager 값을 보고 보통 자동으로 잡힙니다)pnpm install을 먼저 한 번 실행한 뒤 실행 버튼을 누르세요.api/.env) — 셋 중 하나| 하고 싶은 것 | 설정 | AI 가이드 화면 |
|---|---|---|
| AI 없이 실행 (기본값) | GEMINI_API_KEY="", USE_MOCK_AI=false | "AI 꺼짐" 안내. 차종 검색·고질병·데이터 관리는 정상 |
| 키 없이 AI 화면 흐름만 확인 | USE_MOCK_AI=true | 미리 준비된 샘플 답변 (비용·할당량 0) |
| 실제 Gemini 사용 | GEMINI_API_KEY="발급받은 키", USE_MOCK_AI=false | 실제 답변 |
gemini-flash-latest(최신 Flash). 바꾸려면 GEMINI_MODEL.env를 바꾸면 API 서버를 다시 시작하세요.AI_GLOBAL_PER_MIN)까지이고 넘으면 429와 "몇 초 뒤 다시" 안내를 줍니다. 둘 다 서버 메모리에 저장하므로 서버를 여러 대로 늘리면 Redis로 옮겨야 합니다./admin과 쓰기 API(POST·PATCH·DELETE)는 로그인이 필요합니다. 조회(GET)와 AI 질문은 누구나 쓸 수 있어요.
| 역할 | 할 수 있는 것 | 정하는 곳 (api/.env) |
|---|---|---|
| ADMIN | 모든 수정 + "검증됨" 처리 + 삭제 + 차종 추가 | ADMIN_EMAILS |
| EDITOR | 고질병 초안·점검 포인트 작성, 초안 수정, 검토 요청. 승인·반려·검증 취소·삭제는 불가 | EDITOR_EMAILS |
| 그 외 구글 계정 | 로그인 거절 |
@Roles("ADMIN", "EDITOR"), 로그인 없이 되는 곳은 @Public()로 표시합니다(api/src/auth/auth.guard.ts). 새 API를 만들고 깜빡해도 열려 있지 않아요.구글 로그인 켜기 (한 번만)
http://localhost:3201 추가 (배포하면 실제 화면 주소도)api/.env의 GOOGLE_CLIENT_ID에 넣고 API 다시 시작 (클라이언트 보안 비밀번호는 필요 없음)구글 설정 전에도 AUTH_DEV_LOGIN=true면 관리 화면에서 이메일만으로 로그인할 수 있어요(개발·테스트용, 운영 환경에서는 항상 꺼짐). 이메일은 ADMIN_EMAILS·EDITOR_EMAILS에 있어야 합니다.
초안 ──검토 요청──▶ 검토 중 ──승인(출처 필수, ADMIN)──▶ 검증됨
▲ ◀──요청 취소── └──반려(사유 필수, ADMIN)──▶ 반려 ──다시 고치기──▶ 초안
└────────────────── 검증 취소(사유 필수, ADMIN) ◀── 검증됨
api/src/issues/issue-workflow.ts의 표 하나에만 있고, 표에 없는 전이(초안 → 검증됨 직행 등)는 서버가 거절합니다. 관리 화면 버튼도 이 표로 만들어 서버가 내려줍니다.IssueHistory): 작성·수정·상태 전이마다 누가·언제·무엇을(수정한 칸의 전·후)·왜(사유)를 남깁니다. 변경과 이력은 하나의 트랜잭션으로 저장해서 이력 없는 변경이 생기지 않습니다.| 주소 | 내용 |
|---|---|
/ | 사이트 소개: 어려운 점 3가지 → 사용 방법 → 시작 버튼 |
/cars | 차종 선택: 검색 + 제조사 탭(국산 각각, 수입 묶음) + 차 종류 필터(RV/SUV·세단·경차) → 차 이름 카드(이미지 없이 이름 + 차 종류) |
/generations/[maker]/[name] | 세대·연식 선택: "올 뉴 투싼 (2015~2018)"처럼 고르기, 연식 입력으로 좁히기 |
/cars/[id] | 고질병 리포트: 엔진·미션 요약 → 고질병(검증됨 먼저) → 매장 체크리스트(브라우저에 저장) → 공통 점검 |
/cars/[id]/inspect | 매장 점검 가이드: 스펙별 주의점(DCT → 저속 꿀렁거림 등) → 본넷 그림 + 빨간 동그라미 → 시운전 순서 |
/guide | AI: 무엇부터 알아볼지, 용도별 추천 (추천 차종 → 세대 선택으로 연결) |
/admin | 구글 로그인 필요. 차종·엔진 추가, 고질병 추가·검증·삭제, 이 차 본넷 점검 포인트 추가 |
조회 화면은 ISR(/cars, /generations/…, /cars/[id], /cars/[id]/inspect): 서버가 API를 불러 화면을 만들어 저장해 두고, 다음 방문자부터는 저장된 화면을 그대로 줍니다(API 호출 없음). 1시간마다 뒤에서 새로 만들고, 관리 화면에서 데이터를 고치면 바로 지워져 다음 방문 때 반영됩니다(web/src/app/actions.ts). 체크리스트·필터처럼 움직이는 부분만 브라우저 컴포넌트(web/src/components/)입니다.
pnpm build 때 /cars를 미리 만들기 때문에 빌드할 때 API가 켜져 있어야 합니다. 나머지 화면은 첫 방문 때 만들어요.pnpm dev(개발 모드)에서는 저장하지 않고 매번 새로 만듭니다. 캐시 동작은 pnpm build && pnpm start로 확인하세요.api/prisma/schema.prisma)| 테이블 | 내용 |
|---|---|
CarModel | 세대 하나. name(투싼) + displayName(올 뉴 투싼) + 세대 코드 + 연식 구간 + 차급 + 차 종류(bodyType: 세단·SUV·경차·승합) |
Powertrain | 세대별 엔진·미션 조합 (연료, 미션 종류 AT·DCT·CVT·MT). 미션·연료만 넣으면 주의점이 자동으로 붙음 (api/src/cars/powertrain-tips.ts) |
CarIssue | 고질병. 초안(DRAFT) → 검토 중(IN_REVIEW) → 검증됨(VERIFIED, 출처 필수) / 반려(REJECTED) |
IssueHistory | 고질병 변경 이력: 작성·수정(바뀐 칸 전·후)·상태 전이(사유), 한 사람(User) |
User | 관리 화면에 로그인한 구글 계정 + 역할(ADMIN·EDITOR) |
InspectionPoint | 본넷 그림 위 빨간 동그라미. carModelId가 없으면 모든 차 공통, 있으면 그 차만(점선). 위치는 그림 기준 % |
스키마를 고치면 api 폴더에서 pnpm db:migrate (마이그레이션 + Prisma Client 재생성)를 실행하세요.
차종 데이터는 api/prisma/seed-data/(제조사별 파일)에 있습니다. 파일에 차종을 추가한 뒤 api 폴더에서 pnpm db:add-missing 을 실행하면 DB에 없는 것만 추가합니다(기존 데이터 유지). pnpm db:seed는 전부 지우고 다시 넣습니다.
| Method | Path | 설명 |
|---|---|---|
| GET | /cars/models | 차종 선택 화면용: 차 이름 단위 카드 |
| GET | /cars/generations?maker=현대&name=투싼 | 한 차종의 세대들 |
| GET | /cars/search?q=투싼&year=2015 | 이름·세대 이름·별칭 + 연식으로 찾기 |
| GET | /cars/:id | 리포트: 세대 + 엔진·미션 + 주의점 + 고질병 + 공통 점검 |
| GET | /cars/:id/inspection | 점검 가이드: 주의점 + 본넷 포인트 + 시운전 순서 |
| GET | /cars | 세대 전체 (관리용) |
| GET · POST | /auth/config, /auth/google, /auth/dev, /auth/me | 로그인 설정 · 구글 로그인 · 개발용 로그인 · 내 정보 |
| POST | /cars | 세대 + 엔진·미션 추가 🔒ADMIN |
| GET | /issues?carModelId=2 | 관리 화면: 반려 포함 전부 + 이력 + 내가 누를 수 있는 심사 버튼 🔒ADMIN·EDITOR |
| POST · PATCH | /issues, /issues/:id | 고질병 추가(항상 초안)·내용 수정(초안·반려만) 🔒ADMIN·EDITOR |
| POST | /issues/:id/transition | 상태 전이 {"to":"VERIFIED","sourceUrl":"..."} / {"to":"REJECTED","reason":"..."} 🔒역할은 심사 흐름 표대로 |
| DELETE | /issues/:id | 삭제(이력도 같이 지워짐. 틀린 정보는 반려 권장) 🔒ADMIN |
| POST · DELETE | /inspection-points, /inspection-points/:id | 본넷 점검 포인트 추가 🔒ADMIN·EDITOR, 삭제 🔒ADMIN |
🔒 표시는 Authorization: Bearer <로그인 토큰>이 필요합니다. API 문서(/docs)의 Authorize 버튼에 토큰을 넣으면 바로 시험할 수 있어요.
| GET | /health | 서버·DB 상태 (배포 헬스체크) |
| POST | /health/sentry-test | Sentry 연결 확인용 일부러 500 에러 🔒ADMIN |
| GET | /ai/status | AI 모드 (gemini / mock / off) |
| POST | /ai/guide · /ai/recommend | 무엇부터 알아볼지 · 용도별 추천 |
pnpm test # 단위 테스트 (Vitest, api)
pnpm typecheck # api·web·e2e 타입 체크
pnpm lint # web 린트
pnpm test:e2e # E2E (Playwright): 브라우저로 실제 화면을 눌러봄
E2E (e2e/): 테스트 전용으로 DB(car_check_e2e, 개발 DB와 별개)·API(3290)·화면(3291, 운영 빌드)을 띄운 뒤 모바일 크롬으로 확인합니다. 개발 서버가 떠 있어도 겹치지 않아요.
pnpm --dir e2e exec playwright install chromium (브라우저 설치). Docker DB(pnpm --dir api db:up)가 켜져 있어야 합니다.pnpm --dir e2e report로 화면·네트워크 기록(trace)을 볼 수 있어요.CI (.github/workflows/ci.yml): PR과 master 푸시마다 GitHub Actions에서 타입 체크·린트·단위 테스트 → E2E 순서로 돌립니다. DB는 Postgres 서비스 컨테이너, 실패하면 Playwright 리포트를 내려받을 수 있어요.
| 무엇 | 어디 | 설정 파일 |
|---|---|---|
| 화면 (Next.js) | Vercel (싱가포르) | web/vercel.json |
| API (NestJS) | Render 무료 (싱가포르) | render.yaml |
| DB (PostgreSQL) | Neon 무료 (싱가포르) | |
| 에러 수집 | Sentry (Next.js·NestJS 프로젝트 2개) | web/src/instrumentation*.ts, api/src/instrument.ts |
지금 배포된 곳 (2026-10-27)
| 무엇 | 주소 |
|---|---|
| 화면 | https://car-check-fawn-zeta.vercel.app |
| API | https://car-check-api-vfyd.onrender.com (/health로 상태 확인) |
api/.env.render(Render에 붙여 넣기), web/.env.vercel(Vercel에 붙여 넣기). 둘 다 저장소에 올라가지 않습니다(.git/info/exclude).WEB_ORIGIN·NEXT_PUBLIC_API_URL은 https://를 포함하고 끝에 /를 붙이지 않습니다.db:seed는 전부 지우고 다시 넣습니다. 운영 DB에 관리 화면에서 승인한 데이터가 생긴 뒤에는 운영 DB에 시드를 돌리지 말고 관리 화면으로 추가하세요.처음 배포 순서 (다시 만들 때)
-pooler가 들어간 주소)와 Direct.DATABASE_URL="<Direct 주소>" pnpm --dir api exec prisma migrate deploy → DATABASE_URL="<Direct 주소>" pnpm --dir api db:seedrender.yaml대로 생성. 비밀값 입력: DATABASE_URL(Pooled), DIRECT_URL(Direct), ADMIN_EMAILS, GOOGLE_CLIENT_ID, SENTRY_DSN(NestJS), GEMINI_API_KEY(선택). WEB_ORIGIN은 5번 뒤에 채움.web → 환경변수 NEXT_PUBLIC_API_URL(Render 주소), NEXT_PUBLIC_SENTRY_DSN(Next.js) → Deploy. 빌드 때 /cars를 미리 만들므로 Render API가 떠 있어야 합니다(잠들어 있으면 깨우느라 빌드가 조금 느려짐).WEB_ORIGIN에 Vercel 주소 → Google Cloud Console의 승인된 JavaScript 원본에 Vercel 주소 추가.https://<render 주소>/health → {"ok":true}. 관리자 토큰으로 POST /health/sentry-test를 부르면 Sentry에 테스트 에러가 들어옵니다.이후에는 master에 푸시하면 CI(GitHub Actions)가 모두 통과한 뒤에만 Render·Vercel이 자동으로 다시 배포하고, 새 마이그레이션은 Render가 켜질 때 적용합니다.
render.yaml의 autoDeployTrigger: checksPass (CI 통과 후 배포)기획과 일정은 docs/plan.md에 있습니다 (분류 화면 → 엔진룸 사진 → AI로 고질병 후보 모으기 → 커뮤니티 → 추천).