Files
AutonetSellCar/CLAUDE.md
AutonetSellCar Deploy 04bec0d2c7 docs: Add manual/auto deployment conflict warning
- Document git hook vs manual docker run conflict issue
- Add resolution steps for container name conflicts
- Reinforce: always use docker-compose for staging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 23:32:28 +09:00

38 KiB

AutonetSellCar.com 개발 가이드

이 문서는 Claude Code 세션에서 반드시 읽고 참고해야 하는 중요한 정보입니다.

배포 시 반드시 DEPLOYMENT.md도 함께 확인하세요!

  • DB 스키마 변경 시 수동으로 ALTER TABLE 필요
  • 배포 명령어 및 롤백 방법 안내

🖥️ 서버 인프라 요약

서버 구성

서버 IP 역할 주요 서비스
Server1 192.168.0.201 Master nginx-proxy-manager, PostgreSQL, Redis, Portainer, Grafana
Server2 192.168.0.202 Worker AutonetSellCar (Production/Staging)
Server3 192.168.0.203 Worker Grantech, Nextcloud (cloud.grantech.kr)

리버스 프록시 (nginx-proxy-manager)

  • 관리 UI: http://server1:81 (또는 http://192.168.0.201:81)
  • 모든 도메인 SSL 인증서는 nginx-proxy-manager에서 관리
  • 새 도메인 추가 시 → nginx-proxy-manager에서 Proxy Host 추가

등록된 도메인

도메인 Forward Host Forward Port
autonetsellcar.com 192.168.0.202 3000
staging.autonetsellcar.com 192.168.0.202 3001
grantech.kr 192.168.0.203 3001
cloud.grantech.kr 192.168.0.203 8080

데이터베이스

DB 이름 용도 서버
autonet AutonetSellCar Production Server1 (PostgreSQL)
autonet_staging AutonetSellCar Staging Server1 (PostgreSQL)
grantech Grantech 사이트 Server1 (PostgreSQL)

Docker 컨테이너 관리

Server2 (AutonetSellCar)

# 위치: /opt/autonet/staging 또는 /opt/autonet/production
docker compose -f docker-compose.staging.yml ps      # 스테이징 상태
docker compose -f docker-compose.production.yml ps   # 프로덕션 상태

Server3 (Grantech, Nextcloud)

# Grantech (위치: /home/damon/sites/grantech)
ssh server3 "cd /home/damon/sites/grantech && docker compose ps"
ssh server3 "cd /home/damon/sites/grantech && docker compose logs -f"
ssh server3 "cd /home/damon/sites/grantech && docker compose restart"

# Nextcloud
ssh server3 "docker ps | grep nextcloud"
컨테이너 포트 설명
grantech-frontend 3001 Next.js (grantech.kr)
grantech-backend 8001 FastAPI (api.grantech.kr)
nextcloud 8080 Nextcloud (cloud.grantech.kr)

상세 인프라 문서: SERVER_INFRASTRUCTURE_PLAN.md


CRITICAL: 배포 전 필수 확인사항 (DO NOT SKIP!)

🚨 Production 환경 고정값 (절대 변경 금지!)

항목 올바른 값 잘못된 예
DB_NAME autonet mongolcar, autonet_db
DB_PASSWORD roskfl@1122 (@ 포함, URL 인코딩 필요) -
API_URL (Frontend) https://autonetsellcar.com http://192.168.0.202:8000
Uploads 경로 /opt/autonet/production/backend/uploads /home/damon/mongolcar/data/uploads
배포 디렉토리 /opt/autonet/production /home/damon/mongolcar

🚫 절대 하지 말 것

  1. 수동 docker run에서 환경변수 직접 타이핑 금지 - PRODUCTION_VALUES.md에서 복사만 허용
  2. DB_NAME 직접 타이핑 금지 - 반드시 autonet 복사/붙여넣기
  3. .env 파일 덮어쓰기 금지 - 서버의 기존 .env 보존
  4. /home/damon/mongolcar/data/uploads 사용 금지 - 비어있는 잘못된 경로
  5. 스테이징 배포 시 수동 docker run 금지 - 반드시 docker compose 사용! (아래 참고)

⚠️ 스테이징 배포 주의사항 (2025-01-05 사고 사례)

문제: 수동 docker run으로 스테이징 배포 시 볼륨 마운트 경로를 잘못 지정하여 이미지가 사라짐

원인: docker-compose.staging.yml은 production uploads를 마운트하지만, 수동 명령어에서 staging uploads를 마운트함

# docker-compose.staging.yml (올바른 설정)
volumes:
  - /opt/autonet/production/backend/uploads:/app/uploads  # production 폴더 공유!

올바른 스테이징 배포 방법:

# 반드시 docker-compose 사용!
ssh server2 "cd /opt/autonet/staging && docker compose -f docker-compose.staging.yml up -d --build"

# 개별 서비스만 재시작
ssh server2 "cd /opt/autonet/staging && docker compose -f docker-compose.staging.yml up -d --build backend-staging"
ssh server2 "cd /opt/autonet/staging && docker compose -f docker-compose.staging.yml up -d --build frontend-staging"

절대 하지 말 것:

# ❌ 이렇게 하면 안됨! 볼륨 경로가 docker-compose와 달라짐
docker run -v /opt/autonet/staging/backend/uploads:/app/uploads ...

⚠️ 수동 배포와 자동 배포(git hook) 충돌 문제 (2025-01-05 추가 사례)

문제: 수동으로 컨테이너를 만든 후 git push하면 자동 배포가 실패함

원인:

  • 수동 배포: docker run --name autonet-frontend-staging ... → docker-compose가 관리하지 않음
  • 자동 배포: docker compose down → 수동 컨테이너는 중지되지 않음
  • 자동 배포: docker compose up → 컨테이너 이름 충돌로 실패

해결 방법: 수동 컨테이너가 있으면 먼저 제거

# 충돌 시 수동 컨테이너 제거 후 docker-compose 재실행
ssh server2 "docker stop autonet-frontend-staging autonet-backend-staging 2>/dev/null; docker rm autonet-frontend-staging autonet-backend-staging 2>/dev/null"
ssh server2 "cd /opt/autonet/staging && docker compose -f docker-compose.staging.yml up -d"

예방책: 스테이징 배포는 항상 docker-compose만 사용하고, 수동 docker run 절대 금지!

배포 전 필수 검증 명령어

# 1. DB 이름 확인 (반드시 autonet이어야 함!)
ssh server1 "docker exec postgres-primary psql -U admin -d autonet -c 'SELECT COUNT(*) FROM cars;'"

# 2. Uploads 경로에 파일 있는지 확인
ssh server2 "ls /opt/autonet/production/backend/uploads/cars/ | head -5"

# 3. 배포 후 API 확인
curl -s https://autonetsellcar.com/api/hero-banners/ | head -c 100

📋 표준 컨테이너 실행 명령어 (복사해서 사용!)

⚠️ 아래 명령어를 그대로 복사해서 사용하세요. 절대 직접 타이핑하지 마세요!

Backend 실행

ssh server2 "docker stop autonet-backend 2>/dev/null; docker rm autonet-backend 2>/dev/null; docker run -d --name autonet-backend --restart unless-stopped -p 8000:8000 -e USE_SQLITE=False -e DB_HOST=192.168.0.201 -e DB_PORT=5432 -e DB_NAME=autonet -e DB_USER=admin -e 'DB_PASSWORD=roskfl@1122' -e REDIS_HOST=192.168.0.201 -e REDIS_PORT=6379 -e 'REDIS_PASSWORD=roskfl@1122' -e SECRET_KEY=YourSuperSecretKeyForJWT123! -e AGENT_API_KEY=AgentApiKey123! -v /opt/autonet/production/backend/uploads:/app/uploads --network mongolcar-network production-backend"

Frontend 빌드 전 (.env.production 설정)

ssh server2 "echo 'NEXT_PUBLIC_API_URL=https://autonetsellcar.com' > /home/damon/mongolcar/frontend/.env.production"

Frontend 실행

ssh server2 "docker stop autonet-frontend 2>/dev/null; docker rm autonet-frontend 2>/dev/null; docker run -d --name autonet-frontend --restart unless-stopped -p 3000:3000 --network mongolcar-network production-frontend"

🚀 배포 워크플로우 (필수!)

서버 구성

환경 URL 용도
스테이징 https://staging.autonetsellcar.com 테스트용 (먼저 배포)
프로덕션 https://autonetsellcar.com 실서비스 (테스트 후 배포)

CI/CD 자동 배포 흐름 (2025-01-04 적용)

┌─────────────────────────────────────────────────────────────┐
│  1. 로컬 PC에서 코드 수정                                    │
│     D:\Workspace\claudeCode\AutonetSellCar.com\              │
└─────────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  2. Git 커밋 및 푸시 → ★ 스테이징 자동 배포!                 │
│     git add .                                                │
│     git commit -m "변경 내용"                                │
│     git push staging main                                    │
│     → post-receive hook이 자동으로 스테이징 빌드/배포        │
└─────────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  3. 스테이징 테스트                                          │
│     https://staging.autonetsellcar.com 에서 충분히 테스트    │
└─────────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  4. 프로덕션 배포 (deploy.sh promote)                        │
│     ssh server2 "/opt/autonet/scripts/deploy.sh promote"    │
│     → 스테이징 소스를 프로덕션으로 복사 및 빌드/배포         │
└─────────────────────────────────────────────────────────────┘

⚠️ Claude Code 작업 규칙 (중요!)

반드시 CI/CD 프로세스를 따르세요:

  1. 로컬에서만 코드 수정 - 서버 파일 직접 수정 금지!
  2. git push staging main - 스테이징 자동 배포
  3. 테스트 후 promote - 프로덕션 배포
  4. DB 스키마 변경 시 - ALTER TABLE 명령어 별도 실행 필요 (아래 참조)
# ❌ 잘못된 방식 (절대 금지!)
ssh server2 "vim /opt/autonet/production/backend/app/main.py"  # 서버 직접 수정
scp backend/app/main.py server2:/opt/autonet/production/backend/app/  # 수동 복사

# ✅ 올바른 방식
# 1. 로컬에서 수정
# 2. git add . && git commit -m "변경 내용" && git push staging main
# 3. 스테이징 테스트
# 4. ssh server2 "/opt/autonet/scripts/deploy.sh promote"

Git 저장소 정보

항목
원격 저장소 ssh://damon@192.168.0.202/opt/autonet/git/autonet.git
브랜치 main
원격 이름 staging

CI/CD 배포 명령어

# ★ 스테이징 배포 (자동) - git push만 하면 됨!
git add .
git commit -m "변경 내용 설명"
git push staging main
# → post-receive hook이 자동으로:
#   1. /opt/autonet/staging/ 에 소스 체크아웃
#   2. docker compose로 frontend/backend 빌드 및 재시작

# ★ 프로덕션 배포 (수동 promote)
ssh server2 "/opt/autonet/scripts/deploy.sh promote"
# → staging 소스를 production으로 복사 후 빌드/배포

# 상태 확인
ssh server2 "/opt/autonet/scripts/deploy.sh status"

# 롤백 (문제 발생 시)
ssh server2 "/opt/autonet/scripts/deploy.sh rollback"

수동 배포 (비상시에만 사용)

# 스테이징 수동 빌드
ssh server2 "cd /opt/autonet/staging && docker compose -f docker-compose.staging.yml build --no-cache && docker compose -f docker-compose.staging.yml up -d"

# 프로덕션 수동 빌드
ssh server2 "cd /opt/autonet/production && docker compose -f docker-compose.production.yml build --no-cache && docker compose -f docker-compose.production.yml up -d"

Staging vs Production 환경 분리

데이터베이스 분리

환경 DB 이름 용도
Production autonet 실서비스 데이터
Staging autonet_staging 테스트용 데이터 (안전한 테스트)

DB 서버: PostgreSQL on 192.168.0.201:5432

Staging 환경 설정

Backend .env (/opt/autonet/staging/backend/.env):

DB_NAME=autonet_staging  # Production은 autonet

docker-compose.staging.yml:

  • Frontend: NEXT_PUBLIC_API_URL=https://staging.autonetsellcar.com (build arg)
  • Backend: .env 파일을 볼륨 마운트로 연결

소스 코드 동기화 (CI/CD로 자동화됨)

CI/CD 적용 후: git push staging main만 하면 자동으로 스테이징에 배포됩니다.

# 스테이징 배포 (자동)
git push staging main  # post-receive hook이 자동으로 staging에 체크아웃 및 빌드

# 프로덕션 배포 (promote 명령)
ssh server2 "/opt/autonet/scripts/deploy.sh promote"  # staging → production 복사 및 빌드

수동 동기화 (비상시에만):

# Production → Staging 소스 동기화 (일반적으로 필요 없음)
ssh damon@192.168.0.202 "rsync -av --exclude='node_modules' --exclude='.next' /opt/autonet/production/frontend/ /opt/autonet/staging/frontend/"

Staging DB 초기 데이터 복사

# Production → Staging 데이터 복사 (psql 사용)
PGPASSWORD='roskfl@1122' pg_dump -h 192.168.0.201 -U admin -d autonet --data-only -t users -t cars -t car_images -t hero_banners | \
PGPASSWORD='roskfl@1122' psql -h 192.168.0.201 -U admin -d autonet_staging

⚠️ DB 스키마 변경 시 (중요!)

SQLAlchemy 모델 변경만으로는 DB가 자동 업데이트되지 않습니다!

코드에서 새 컬럼을 추가하면, 반드시 수동으로 ALTER TABLE을 실행해야 합니다:

# 1. 먼저 Staging DB에 적용하여 테스트
ssh server1 "docker exec -it postgres-primary psql -U admin -d autonet_staging -c \"
ALTER TABLE users ADD COLUMN IF NOT EXISTS new_column VARCHAR(255) DEFAULT '';
\""

# 2. 테스트 후 Production DB에 적용
ssh server1 "docker exec -it postgres-primary psql -U admin -d autonet -c \"
ALTER TABLE users ADD COLUMN IF NOT EXISTS new_column VARCHAR(255) DEFAULT '';
\""

DB 스키마 변경 체크리스트:

  1. backend/app/models/ 에서 모델 수정
  2. Staging DB에 ALTER TABLE 실행
  3. git push staging main (코드 배포)
  4. Staging에서 테스트
  5. Production DB에 ALTER TABLE 실행
  6. deploy.sh promote (프로덕션 배포)

최근 추가된 컬럼 예시 (2025-01-04):

-- users 테이블
ALTER TABLE users ADD COLUMN IF NOT EXISTS cash_cc_balance FLOAT DEFAULT 0;

-- system_settings 테이블
ALTER TABLE system_settings ADD COLUMN IF NOT EXISTS marketing_enabled BOOLEAN DEFAULT false;
ALTER TABLE system_settings ADD COLUMN IF NOT EXISTS marketing_telegram_enabled BOOLEAN DEFAULT false;
ALTER TABLE system_settings ADD COLUMN IF NOT EXISTS marketing_telegram_bot_token VARCHAR(255) DEFAULT '';
ALTER TABLE system_settings ADD COLUMN IF NOT EXISTS marketing_telegram_channel_id VARCHAR(255) DEFAULT '';
ALTER TABLE system_settings ADD COLUMN IF NOT EXISTS marketing_telegram_message_template TEXT DEFAULT '';
ALTER TABLE system_settings ADD COLUMN IF NOT EXISTS marketing_facebook_enabled BOOLEAN DEFAULT false;
ALTER TABLE system_settings ADD COLUMN IF NOT EXISTS marketing_instagram_enabled BOOLEAN DEFAULT false;
ALTER TABLE system_settings ADD COLUMN IF NOT EXISTS marketing_auto_post_on_banner BOOLEAN DEFAULT false;

1. 프로젝트 구조

AutonetSellCar.com/
├── backend/                    # FastAPI 백엔드
│   ├── app/
│   │   ├── api/               # API 라우터
│   │   ├── models/            # SQLAlchemy 모델
│   │   ├── services/          # 비즈니스 로직
│   │   └── database.py        # DB 연결
│   ├── uploads/               # 업로드 파일 저장
│   │   └── performance_checks/ # 성능점검표 PDF
│   ├── autonet.db             # ★ 실제 사용하는 DB (이것만 사용!)
│   └── venv/                  # Python 가상환경
├── frontend/                  # Next.js 프론트엔드
│   └── src/
│       ├── app/               # 페이지
│       └── lib/api.ts         # API 함수
└── restart-dev.bat            # 개발 서버 재시작 스크립트

2. 중요: DB 파일 관리

★★★ 반드시 확인 ★★★

실제 사용하는 DB 파일: backend/autonet.db (이것만!)

과거에 테스트용으로 생성된 빈 DB 파일들이 있을 수 있습니다:

  • autonet.db (루트) - 사용하지 않음
  • autonetsellcar.db - 사용하지 않음
  • car_platform.db - 사용하지 않음

이런 파일들이 발견되면 삭제해야 합니다.

# DB 파일 확인 스크립트
from pathlib import Path
base = Path(r'D:\Workspace\claudeCode\AutonetSellCar.com')
for db in base.rglob('*.db'):
    if 'venv' not in str(db) and 'node_modules' not in str(db):
        print(f"{db}: {db.stat().st_size / 1024:.1f} KB")

3. 성능점검표 PDF 시스템

3.1 PDF 생성 흐름

  1. 관리자가 Carmodoo에서 차량 검색
  2. 차량 가져오기(import) 시 check_num (성능점검번호) 추출
  3. capture_performance_check_pdf() 함수로 PDF 캡처 시도
  4. 성공 시 car_performance_checks.pdf_path에 경로 저장
  5. 프론트엔드에서 pdf_path가 있으면 PDF 보기 버튼 표시

3.2 PDF 버튼이 안 보이는 경우

원인: pdf_path가 NULL인 경우

해결 방법:

  1. 관리자 > Hero Banners 페이지에서 "PDF 재시도" 버튼 클릭
  2. 또는 API 직접 호출: POST /api/carmodoo/admin/retry-all-failed-pdfs

3.3 PDF 관련 파일들

  • backend/app/services/pdf_service.py - PDF 캡처 로직 (3회 자동 재시도)
  • backend/app/api/carmodoo.py - PDF 관련 API 엔드포인트
  • frontend/src/app/cars/[id]/page.tsx - PDF 보기 버튼 (라인 605 근처)
// PDF 버튼 표시 조건 (page.tsx)
{performanceCheck.data?.pdf_path && (
  // PDF 버튼 렌더링
)}

4. check_num (성능점검번호) 처리

4.1 check_num 추출 위치

Carmodoo HTML에서 checkNum주석 처리된 부분에 있음:

<!-- ... checkNum=9830018360 ... -->

4.2 파싱 로직 (carmodoo.py)

# 전체 row HTML에서 checkNum 추출 (주석 포함)
row_html = etree.tostring(row, encoding='unicode')
check_match = re.search(r'checkNum=(\d+)', row_html)
if check_match:
    check_num = check_match.group(1)

4.3 check_num 관련 모델/인터페이스

백엔드 (carmodoo.py):

class CarmodooSearchResultItem(BaseModel):
    # ... 기타 필드 ...
    check_num: Optional[str] = None  # 성능점검번호

프론트엔드 (api.ts):

export interface CarmodooSearchResult {
  // ... 기타 필드 ...
  check_num?: string;  // 성능점검번호
}

5. 서버 재시작 관련

5.1 uvicorn 캐시 문제

증상: 코드 수정 후에도 API 스키마에 변경사항이 반영되지 않음

원인:

  • --reload 옵션이 있어도 일부 변경사항이 적용되지 않는 경우 있음
  • __pycache__ 캐시 문제

해결 방법:

# 1. __pycache__ 삭제
Get-ChildItem -Path 'backend' -Recurse -Directory -Filter '__pycache__' | Remove-Item -Recurse -Force

# 2. 서버 완전 재시작
# 방법 A: restart-dev.bat 실행
# 방법 B: 수동으로 uvicorn 프로세스 종료 후 재시작

5.2 포트 확인

netstat -ano | findstr :8000
netstat -ano | findstr :3000

6. 관리자 기능

6.1 Hero Banners 페이지 (/admin/hero-banners)

차량 검색 필터:

  • 제조사, 모델, 등급
  • 연식 (From ~ To)
  • 연료, 배기량, 주행거리

PDF 관리:

  • "PDF 재시도" 버튼 - PDF 없는 모든 차량에 대해 PDF 재생성

6.2 관리자 API 엔드포인트

엔드포인트 설명
GET /api/carmodoo/admin/pdf-failures PDF 생성 실패 목록
POST /api/carmodoo/admin/retry-all-failed-pdfs 전체 PDF 재시도
POST /api/carmodoo/regenerate-pdf/{car_id} 특정 차량 PDF 재생성

7. 자주 발생하는 문제와 해결

7.1 "PDF 버튼이 안 보여요"

체크리스트:

  1. 로그인 상태 확인 (로그인 사용자만 PDF 접근 가능)
  2. DB에서 해당 차량의 pdf_path 확인
    SELECT car_id, check_number, pdf_path FROM car_performance_checks WHERE car_id = ?;
    
  3. pdf_path가 NULL이면 → PDF 재생성 필요
  4. PDF 파일이 실제로 존재하는지 확인: backend/uploads/performance_checks/

7.2 "코드 수정했는데 적용이 안 돼요"

  1. __pycache__ 삭제
  2. uvicorn 서버 완전 재시작
  3. 브라우저 캐시 클리어 (Ctrl+Shift+R)

7.3 "검색 결과가 안 나와요"

  1. 캐시 만료 확인: carmodoo_search_cache 테이블
  2. 제조사/모델 코드 확인 (예: 기아=2, K5=38)
  3. 연도 범위 확인

8. 개발 환경 시작

# 방법 1: 배치 파일 사용
restart-dev.bat

# 방법 2: 수동 시작
# 터미널 1 (백엔드)
cd backend
venv\Scripts\activate
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

# 터미널 2 (프론트엔드)
cd frontend
npm run dev

9. 유용한 디버깅 명령어

# DB 상태 확인
import sqlite3
conn = sqlite3.connect('backend/autonet.db')
cursor = conn.cursor()

# 성능점검 데이터 확인
cursor.execute('SELECT car_id, check_number, pdf_path FROM car_performance_checks')
for row in cursor.fetchall():
    print(row)

# PDF 파일 존재 확인
from pathlib import Path
pdf_dir = Path('backend/uploads/performance_checks')
for pdf in pdf_dir.glob('*.pdf'):
    print(f"{pdf.name}: {pdf.stat().st_size / 1024:.1f} KB")

10. 환율 시스템

10.1 개요

한국수출입은행 API에서 실시간 환율을 가져와 모든 가격 표시에 적용합니다.

  • 갱신 주기: 매일 11:30 AM (한국수출입은행 고시 시간)
  • 캐싱: 30분간 캐시 유지
  • 폴백: API 실패 시 기본값 사용

10.2 환율 API 엔드포인트

엔드포인트 설명
GET /api/exchange-rate 전체 환율 정보 (상세)
GET /api/exchange-rate/simple 간단한 환율 정보 (USD, EUR, JPY 등)

응답 예시 (/api/exchange-rate/simple):

{
  "USD": {"rate": 1483.4, "symbol": "$", "name": "미국 달러"},
  "EUR": {"rate": 1749.74, "symbol": "€", "name": "유로"},
  "JPY": {"rate": 9.5001, "symbol": "¥", "name": "일본 옌"}
}

10.3 프론트엔드 환율 Store

파일: frontend/src/lib/exchangeRateStore.ts

// 환율 가져오기
const store = useExchangeRateStore.getState();
const usdRate = store.rates.USD?.rate || 1483;  // 폴백 값

// KRW → USD 변환
const usdAmount = krwAmount / usdRate;

// USD → KRW 변환
const krwAmount = usdAmount * usdRate;

기본값 (API 실패 시):

통화 rate (1 단위당 KRW)
USD 1483
EUR 1750
JPY 9.5
CNY 203
MNT 0.43
RUB 14.5

10.4 환율 적용 파일들

파일 용도
frontend/src/lib/exchangeRateStore.ts Zustand 환율 스토어
frontend/src/lib/i18n.ts formatPriceWithCurrency() 함수
frontend/src/app/exchange-rate/page.tsx 환율 정보 페이지
frontend/src/app/cost/page.tsx 비용 계산기
frontend/src/components/SearchFilters.tsx 검색 필터 가격 표시
frontend/src/app/admin/purchased/page.tsx 구매 차량 관리

10.5 주의사항

하드코딩된 환율 사용 금지!

// 잘못된 예
const usd = krwAmount * 0.00069;  // 하드코딩 X
const krw = usdAmount * 1333;     // 하드코딩 X

// 올바른 예
const usdRate = useExchangeRateStore.getState().rates.USD?.rate || 1483;
const usd = krwAmount / usdRate;
const krw = usdAmount * usdRate;

10.6 환율 디버깅

# API 테스트
curl http://localhost:8000/api/exchange-rate/simple

# DB 환율 데이터 확인
sqlite3 backend/autonet.db "SELECT * FROM exchange_rates ORDER BY updated_at DESC LIMIT 5;"

11. 다국어 번역 시스템

11.1 개요

차량 정보(차량명, 연료, 변속기, 색상 등)를 사용자 선택 언어로 번역합니다.

  • 지원 언어: 한국어(ko), 영어(en), 몽골어(mn), 러시아어(ru)
  • 기본값: 해당 언어 번역이 없으면 영어(en)로 대체

11.2 번역 관련 파일들

파일 역할
frontend/src/lib/i18n.ts 정적 번역 사전 (CAR_TRANSLATIONS), translateCarName() 함수
frontend/src/lib/useTranslate.ts translate() 훅 - API + 정적 번역 폴백

11.3 CAR_TRANSLATIONS 구조

const CAR_TRANSLATIONS: Record<string, Record<string, string>> = {
  // 연료 타입
  '휘발유': { ko: '휘발유', en: 'Gasoline', mn: 'Бензин', ru: 'Бензин' },
  '경유': { ko: '경유', en: 'Diesel', mn: 'Дизель', ru: 'Дизель' },

  // 변속기
  '오토': { ko: '오토', en: 'Auto', mn: 'Авто', ru: 'Авто' },
  '수동': { ko: '수동', en: 'Manual', mn: 'Механик', ru: 'Механика' },

  // 제조사
  'KG모빌리티(쌍용)': { ko: 'KG모빌리티(쌍용)', en: 'KG Mobility (SsangYong)', ... },

  // 모델
  '렉스턴 스포츠': { ko: '렉스턴 스포츠', en: 'Rexton Sports', ... },

  // 색상
  '흰색': { ko: '흰색', en: 'White', mn: 'Цагаан', ru: 'Белый' },
};

11.4 번역 함수 사용법

방법 1: useTranslate (권장)

import { useTranslate } from '@/lib/useTranslate';

function Component() {
  const { translate } = useTranslate();
  return <span>{translate(car.fuel)}</span>;  // '휘발유' → 'Gasoline'
}

방법 2: translateCarName 함수 (직접 호출)

import { translateCarName } from '@/lib/i18n';

const translatedFuel = translateCarName(car.fuel, language);  // '경유' → 'Diesel'

11.5 새 번역 추가하기

CAR_TRANSLATIONS에 새 항목 추가:

'새로운용어': { ko: '새로운용어', en: 'New Term', mn: 'Шинэ нэр', ru: 'Новый термин' },

주의: 긴 문자열을 먼저 매칭하기 위해 SORTED_CAR_KEYS가 자동으로 정렬됨

11.6 번역이 안 되는 경우

  1. CAR_TRANSLATIONS에 해당 용어가 없음 → 새 항목 추가
  2. 프론트엔드 리빌드 필요npm run dev 재시작 또는 .next 폴더 삭제
  3. translate() 함수 미사용 → 페이지에서 직접 값 출력 중 (수정 필요)
  4. localStorage에 'ko' 언어 저장됨 → 일반 유저는 한국어 선택 불가지만 localStorage에 남아있으면 번역 스킵됨
    • LanguageSelector.tsx에서 자동으로 영어로 리셋하도록 수정됨

11.7 언어 선택 제한

  • 관리자(is_admin=true): 한국어, 영어, 몽골어, 러시아어 모두 선택 가능
  • 일반 유저: 영어, 몽골어, 러시아어만 선택 가능 (한국어 숨김)
  • 파일: frontend/src/components/LanguageSelector.tsx

12. CC (Car Credit) 시스템

12.1 CC란?

  • 플랫폼 내 가상 화폐
  • 차량 추천 서비스에 사용 (1 CC = N대 추천, 관리자 설정 가능)
  • 신규 가입 시 1 CC 지급
  • 관리자 설정: /admin/settings에서 Cars per CC 값으로 1 CC당 추천 대수 조절 가능 (기본값: 3대)

12.2 CC 충전 패키지

충전 금액 받는 CC 추천 가능 차량 (기본 3대/CC) 할인율
$10 10 CC 30대 -
$27 30 CC 90대 10%
$40 50 CC 150대 20%

참고: 추천 가능 차량 수는 관리자 설정의 Cars per CC 값에 따라 변동됩니다.

12.3 결제 수단

수단 대상 처리 방식
Stripe 몽골 사용자 Visa/Mastercard 자동 결제
몽골 파트너 계좌 러시아 사용자 수동 충전 요청 → 관리자 승인

12.4 Stripe 설정

환경변수 (backend/.env):

STRIPE_SECRET_KEY=sk_test_...      # Stripe 비밀키
STRIPE_PUBLISHABLE_KEY=pk_test_... # Stripe 공개키
STRIPE_WEBHOOK_SECRET=whsec_...    # Webhook 시크릿
STRIPE_SUCCESS_URL=https://yourdomain.com/cc/success
STRIPE_CANCEL_URL=https://yourdomain.com/cc

API 엔드포인트:

엔드포인트 설명
GET /api/cc/packages CC 패키지 목록
POST /api/cc/create-checkout-session Stripe 결제 세션 생성
POST /api/cc/webhook Stripe Webhook 수신
GET /api/cc/checkout-success 결제 완료 확인
POST /api/cc/manual-request 수동 충전 요청 (러시아용)

Stripe Webhook 설정:

  1. Stripe Dashboard → Webhooks
  2. 엔드포인트 추가: https://yourdomain.com/api/cc/webhook
  3. 이벤트 선택: checkout.session.completed, checkout.session.expired

12.5 CC vs 차량 열람

중요: CC는 추천 서비스에만 사용됩니다!

기능 필요 조건
차량 정보 열람 (이미지, 딜러, 성능점검표) 로그인만 하면 무료
배너 차량 열람 비로그인도 무료
차량 추천 서비스 CC 필요 (1 CC = N대, 설정에 따름)

13. 상세사양조회 시스템 (AUTOBEGINS)

13.1 개요

Carmodoo 딜러 포탈의 AUTOBEGINS 서비스를 통해 차량번호 기반 상세사양을 조회합니다.

  • 데이터 소스: AUTOBEGINS (api.autobegins.com) via Carmodoo iframe
  • 조회 방식: Playwright 브라우저 자동화
  • 저장 시점: 배너 등록(import) 시 자동 저장

13.2 조회 가능 정보

카테고리 정보
기본 정보 제조사, 모델명, 등급, 연식
엔진/구동 배기량, 연료, 변속기, 구동방식
성능 최대출력, 최대토크, 연비
차체 차체형식, 도어수, 승차정원, 전장/전폭/전고/휠베이스
옵션 안전옵션, 편의옵션, 외장옵션, 내장옵션
가격 출고가, 기본가, 옵션가

13.3 관련 파일

파일 역할
backend/app/services/spec_service.py Playwright 기반 사양 조회
backend/app/models/car_specification.py CarSpecification 모델
backend/app/api/carmodoo.py /specifications/{car_number}, /car/{car_id}/specifications API

13.4 딜러 상세설명 (dealer_description)

  • cars.dealer_description 컬럼에 저장
  • Carmodoo 상세페이지에서 딜러가 작성한 설명 추출
  • 차량 상세 페이지에서 amber 배경으로 표시 (로그인 사용자)

추출 방식 (2024-12-25 개선):

  1. 검색 결과에서 car_key 추출 (dealerCarviewPopup('...') 패턴)
  2. dealerCarView.html?key=<car_key> URL로 상세 페이지 접근
  3. <div class="carViewMemoWrap"><h3>상세설명</h3><div class="memo">...</div></div> 파싱
# carmodoo.py - 딜러 설명 추출 (car_key 기반)
async def get_car_detail(self, car_no: str, car_key: str = "") -> dict:
    if car_key:
        url = f"{CARMODOO_BASE_URL}/car/dealerCarView.html"
        params = {"key": car_key, "tabStart": "1"}
        # EUC-KR 디코딩 후 상세설명 추출
        # <div class="carViewMemoWrap">...<div class="memo">설명</div></div>

관련 필드:

  • CarmodooSearchResultItem.car_key - 검색 결과에 포함
  • AdminSearchResultItem.car_key - 관리자 검색 결과에 포함
  • ImportCarRequest.car_key - Import 요청에 포함

14. Quote Request 시스템 (차량 추천 요청)

14.1 개요

사용자가 원하는 차량 조건을 입력하면 관리자가 맞춤 차량을 추천해주는 서비스입니다.

  • 비용: 1 CC per request
  • 응답 시간: 24시간 이내 추천
  • 페이지: /vehicle-request

14.2 요청 조건

필드 필수 설명
제조사 기아, 현대 등
모델 K5, 소나타 등
등급 - 선택 사항
연식 범위 - 2020 ~ 2024
주행거리 - 만km 단위
연료 - 휘발유, 경유, 하이브리드 등
배기량 - 1000cc ~ 5000cc

14.3 CC 결제 흐름

  1. 사용자가 조건 입력
  2. 제출 시 CC 잔액 확인 (1 CC 필요)
  3. 잔액 부족 시 /cc 페이지로 안내
  4. CC 차감 후 요청 생성
  5. vehicle_requests.cc_paid 컬럼에 기록

14.4 관련 파일

파일 역할
backend/app/api/vehicle_requests.py 요청 생성 API (CC 차감)
backend/app/models/vehicle_request.py VehicleRequest 모델 (cc_paid 컬럼)
frontend/src/app/vehicle-request/page.tsx 요청 폼 UI (CC 안내)

15. 현지딜러 시스템

15.1 딜러 등급 및 수수료

몽골 마진(5%)에서 딜러 수수료 지급:

등급 조건 수수료율
일반 (Standard) 기본 3.0%
실버 (Silver) 10건+ 판매 3.5%
골드 (Gold) 30건+ 판매 4.0%
플래티넘 (Platinum) 100건+ 판매 4.5%

15.2 레퍼럴 시스템

  • 딜러가 고객에게 추천 코드 제공
  • 고객이 차량 구매 시 딜러에게 수수료 지급
  • 1단계 직접 추천만 인정 (다단계 아님)

16. 딜러 설명 번역 시스템

16.1 개요

딜러 설명(dealer_description)을 Azure Translator API를 사용하여 다국어로 번역합니다.

  • 번역 API: Microsoft Azure Translator (한국어 → 영어/몽골어/러시아어 직접 지원)
  • 무료 한도: 월 200만 글자
  • 저장 시점: Import 시 자동 번역, 관리자가 확인 후 배너/추천 전송

16.2 DB 필드

컬럼 설명
cars.dealer_description 한국어 원문
cars.dealer_description_en 영어 번역
cars.dealer_description_mn 몽골어 번역
cars.dealer_description_ru 러시아어 번역

16.3 환경 변수

# Azure Translator API
AZURE_TRANSLATOR_KEY=your_api_key
AZURE_TRANSLATOR_REGION=koreacentral

16.4 관련 파일

파일 역할
backend/app/services/translation_service.py Azure Translator 연동
backend/app/api/carmodoo.py 번역 관리 API 엔드포인트
frontend/src/app/admin/dealer-translations/page.tsx 관리자 번역 확인/수정 UI
frontend/src/app/cars/[id]/page.tsx 사용자 페이지 번역 표시

16.5 관리자 API 엔드포인트

엔드포인트 설명
GET /api/carmodoo/car/{car_id}/translations 차량 번역 조회
PUT /api/carmodoo/car/{car_id}/translations 차량 번역 수정
POST /api/carmodoo/car/{car_id}/translations/regenerate 번역 재생성
GET /api/carmodoo/admin/untranslated-cars 미번역 차량 목록
POST /api/carmodoo/admin/translate-all-pending 일괄 번역

16.6 번역 흐름

Import 시 자동 번역 (Azure API)
       ↓
관리자 확인 (/admin/dealer-translations)
       ↓
필요시 수정 또는 재번역
       ↓
배너 등록 / 추천 전송
       ↓
사용자 페이지에서 한국어 원문 + 선택언어 번역 표시

17. 변경 이력

날짜 변경 내용
2025-01-04 CI/CD 자동 배포 시스템 구축: git push → 스테이징 자동 배포, deploy.sh promote → 프로덕션 배포
2025-01-04 docker-compose 통합: 개별 docker run 대신 docker-compose.staging.yml/production.yml 사용
2025-01-04 CLAUDE.md CI/CD 워크플로우 문서화: 서버 직접 수정 금지, DB 스키마 변경 프로세스 추가
2025-01-03 Staging DB 분리: autonet_staging DB 생성, Production/Staging 환경 완전 분리
2025-01-03 My Vehicles 기능: 직접 구매 차량을 My Request 페이지에 표시
2024-12-31 Admin Settings 기능 추가: Show Dealer Comment 토글, Korean Domestic + Export Customs 금액 설정
2024-12-31 Cost 페이지에서 국내비용+수출통관비용 동적 적용 (settings API 연동)
2024-12-31 Visitor Stats 국가별 통계 강화: 국기 이모지 추가, 전용 Country Stats 카드 추가
2024-12-31 Hero Banners API 라우트 순서 수정 (422 에러 해결)
2024-12-31 Banner Toggle 로직 수정 (HeroBanner 테이블 기준으로 변경)
2024-12-27 딜러 설명 번역 시스템 추가: Azure Translator API 연동, 한국어→영어/몽골어/러시아어 직접 번역
2024-12-27 관리자 번역 관리 페이지 추가 (/admin/dealer-translations)
2024-12-27 DB 스키마 확장: dealer_description_en/mn/ru 컬럼 추가
2024-12-25 딜러 상세설명 추출 방식 개선: car_key 기반 dealerCarView.html 사용 (기존 carPopView.html 404 문제 해결)
2024-12-25 검색 결과에 car_key 필드 추가 (CarmodooSearchResultItem, AdminSearchResultItem)
2024-12-25 Import 시 car_key 전달하여 딜러 설명 자동 추출
2024-12-25 상세사양조회 시스템 추가 (AUTOBEGINS, spec_service.py, CarSpecification 모델)
2024-12-25 딜러 상세설명 필드 추가 (cars.dealer_description)
2024-12-25 Quote Request 1CC 결제 시스템 추가 (vehicle_requests.cc_paid)
2024-12-25 CC당 추천 대수 관리자 설정 추가 (cars_per_cc, 기본값 3대)
2024-12-25 차량 검색 시 연료 조건 필터링 버그 수정
2024-12-24 Stripe 결제 연동 (CC 충전 패키지, Checkout Session, Webhook)
2024-12-24 CC 시스템 변경 (추천 서비스 기반, 차량열람 무료화)
2024-12-24 언어 자동 리셋 버그 수정 (localStorage 'ko' 문제)
2024-12-24 다국어 번역 시스템 개선 (연료/변속기/색상/차량명)
2024-12-24 환율 시스템 동적 적용 (하드코딩 제거, API 연동)
2024-12-24 PDF 재시도 로직 추가 (3회 자동 재시도)
2024-12-24 빈 DB 파일 정리
2024-12-24 관리자 PDF 재시도 UI/API 추가
2024-12-24 Hero Banners 검색에 주행거리 필터 추가

이 문서는 새 세션 시작 시 반드시 읽어주세요!