From b1afea79d9e9d552ec88ff72b46b852526d942c6 Mon Sep 17 00:00:00 2001 From: AutonetSellCar Deploy Date: Sat, 3 Jan 2026 09:05:16 +0900 Subject: [PATCH] Add SOLD OUT badge and improve deployment docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add SOLD OUT overlay on car detail page image - Add SOLD OUT badge next to car name - Add Sold Out status in Admin Cars detail view - Add soldout field to Car TypeScript interface - Create PRODUCTION_VALUES.md for deployment reference - Update CLAUDE.md with CRITICAL deployment section - Update TROUBLESHOOTING.md with recurring errors ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 53 ++++++++ DEPLOYMENT_GUIDE.md | 5 +- PRODUCTION_VALUES.md | 179 +++++++++++++++++++++++++++ TROUBLESHOOTING.md | 153 +++++++++++++++++++++++ backend/app/api/vehicle_requests.py | 85 ++++++++++++- frontend/src/app/admin/cars/page.tsx | 120 +++++++++++++++--- frontend/src/app/cars/[id]/page.tsx | 22 +++- frontend/src/types/index.ts | 1 + 8 files changed, 593 insertions(+), 25 deletions(-) create mode 100644 PRODUCTION_VALUES.md diff --git a/CLAUDE.md b/CLAUDE.md index bde5b6e..ff97d12 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,59 @@ --- +# โ›” 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](./PRODUCTION_VALUES.md)์—์„œ ๋ณต์‚ฌ๋งŒ ํ—ˆ์šฉ +2. **DB_NAME ์ง์ ‘ ํƒ€์ดํ•‘ ๊ธˆ์ง€** - ๋ฐ˜๋“œ์‹œ `autonet` ๋ณต์‚ฌ/๋ถ™์—ฌ๋„ฃ๊ธฐ +3. **`.env` ํŒŒ์ผ ๋ฎ์–ด์“ฐ๊ธฐ ๊ธˆ์ง€** - ์„œ๋ฒ„์˜ ๊ธฐ์กด `.env` ๋ณด์กด +4. **`/home/damon/mongolcar/data/uploads` ์‚ฌ์šฉ ๊ธˆ์ง€** - ๋น„์–ด์žˆ๋Š” ์ž˜๋ชป๋œ ๊ฒฝ๋กœ + +## โœ… ๋ฐฐํฌ ์ „ ํ•„์ˆ˜ ๊ฒ€์ฆ ๋ช…๋ น์–ด + +```bash +# 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 ์‹คํ–‰ +```bash +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 ์„ค์ •) +```bash +ssh server2 "echo 'NEXT_PUBLIC_API_URL=https://autonetsellcar.com' > /home/damon/mongolcar/frontend/.env.production" +``` + +### Frontend ์‹คํ–‰ +```bash +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" +``` + +--- + ## 1. ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ``` diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md index 8ebf872..431d5b6 100644 --- a/DEPLOYMENT_GUIDE.md +++ b/DEPLOYMENT_GUIDE.md @@ -2,6 +2,9 @@ ์ด ๋ฌธ์„œ๋Š” ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜์™€ ์ฝ”๋“œ ์ˆ˜์ •๋ถ€ํ„ฐ ์šด์˜ ๋ฐฐํฌ๊นŒ์ง€์˜ ์ „์ฒด ๊ณผ์ •์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. +> **์ค‘์š”**: ๋ฐฐํฌ ์ „ ๋ฐ˜๋“œ์‹œ [PRODUCTION_VALUES.md](./PRODUCTION_VALUES.md)์˜ ๊ฐ’์„ ํ™•์ธํ•˜์„ธ์š”! +> ๋ฐ˜๋ณต๋˜๋Š” ์˜ค๋ฅ˜ ํ•ด๊ฒฐ์€ [TROUBLESHOOTING.md](./TROUBLESHOOTING.md)๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”. + --- ## 1. ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์š” @@ -150,7 +153,7 @@ docker exec postgres-primary env | grep POSTGRES # ์ถœ๋ ฅ: # POSTGRES_USER=admin # POSTGRES_PASSWORD=roskfl@1122 -# POSTGRES_DB=mongolcar +# POSTGRES_DB=mongolcar (์ปจํ…Œ์ด๋„ˆ ๊ธฐ๋ณธ DB, AutonetSellCar๋Š” autonet DB ์‚ฌ์šฉ!) ``` ### 4.2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์„ฑ diff --git a/PRODUCTION_VALUES.md b/PRODUCTION_VALUES.md new file mode 100644 index 0000000..75d9b66 --- /dev/null +++ b/PRODUCTION_VALUES.md @@ -0,0 +1,179 @@ +# AutonetSellCar Production Values (Single Source of Truth) + +**์ด ํŒŒ์ผ์˜ ๊ฐ’๋“ค์€ ์ ˆ๋Œ€ ๋ณ€๊ฒฝํ•˜์ง€ ๋งˆ์„ธ์š”!** +**DO NOT MODIFY THESE VALUES!** + +--- + +## Server Information + +| ์„œ๋ฒ„ | IP | ์—ญํ•  | +|------|-----|------| +| Server1 | 192.168.0.201 | PostgreSQL Primary | +| Server2 | 192.168.0.202 | Production (Frontend + Backend) | +| Server3 | 192.168.0.203 | Grantech.kr | +| Server4 | 192.168.0.204 | Development | + +--- + +## Database Configuration + +| ํ•ญ๋ชฉ | ๊ฐ’ | ๋น„๊ณ  | +|------|-----|------| +| **DB_NAME** | `autonet` | ~~mongolcar~~ ์ ˆ๋Œ€ ์•„๋‹˜! | +| **DB_USER** | `admin` | | +| **DB_PASSWORD** | `roskfl@1122` | `@` ํฌํ•จ, URL ์ธ์ฝ”๋”ฉ ํ•„์š” | +| **DB_HOST** | `192.168.0.201` | Server1 | +| **DB_PORT** | `5432` | PostgreSQL ๊ธฐ๋ณธ ํฌํŠธ | + +### Database URL Format + +``` +postgresql://admin:roskfl%401122@192.168.0.201:5432/autonet + ^^^^^^^^ + @๊ฐ€ %40์œผ๋กœ ์ธ์ฝ”๋”ฉ๋จ +``` + +### config.py์—์„œ quote_plus() ํ•„์ˆ˜ + +```python +from urllib.parse import quote_plus +encoded_password = quote_plus(self.DB_PASSWORD) # roskfl@1122 โ†’ roskfl%401122 +``` + +--- + +## Docker Container Configuration + +### Backend Container + +```bash +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" \ + -v /opt/autonet/production/backend/uploads:/app/uploads \ + production-backend +``` + +**์ฃผ์˜์‚ฌํ•ญ:** +- `DB_NAME=autonet` (์ ˆ๋Œ€ mongolcar ์•„๋‹˜!) +- `-v /opt/autonet/production/backend/uploads:/app/uploads` (์ ˆ๋Œ€ `/home/damon/mongolcar/...` ์•„๋‹˜!) +- `DB_PASSWORD`์— `@` ํฌํ•จ๋จ - ๋”ฐ์˜ดํ‘œ๋กœ ๊ฐ์‹ธ๊ธฐ + +### Frontend Container + +```bash +# ๋นŒ๋“œ ์ „ .env.production ํ™•์ธ! +echo 'NEXT_PUBLIC_API_URL=https://autonetsellcar.com' > /opt/autonet/production/frontend/.env.production + +# ๋นŒ๋“œ (๋ฐ˜๋“œ์‹œ --no-cache) +docker build --no-cache -t production-frontend . + +# ์‹คํ–‰ +docker run -d \ + --name autonet-frontend \ + --restart unless-stopped \ + -p 3000:3000 \ + production-frontend +``` + +**์ฃผ์˜์‚ฌํ•ญ:** +- `NEXT_PUBLIC_API_URL=https://autonetsellcar.com` (์ ˆ๋Œ€ `http://192.168.0.202:8000` ์•„๋‹˜!) +- HTTPS ํ•„์ˆ˜ (Mixed Content ๋ฐฉ์ง€) + +--- + +## File Paths + +| ์šฉ๋„ | ์˜ฌ๋ฐ”๋ฅธ ๊ฒฝ๋กœ | ์ž˜๋ชป๋œ ๊ฒฝ๋กœ | +|------|-------------|-------------| +| **Uploads** | `/opt/autonet/production/backend/uploads` | `/home/damon/mongolcar/data/uploads` | +| **Backend ์†Œ์Šค** | `/opt/autonet/production/backend` | - | +| **Frontend ์†Œ์Šค** | `/opt/autonet/production/frontend` | - | + +--- + +## Environment Variables + +### Backend (.env) + +```env +USE_SQLITE=False +DB_HOST=192.168.0.201 +DB_PORT=5432 +DB_NAME=autonet +DB_USER=admin +DB_PASSWORD=roskfl@1122 +``` + +### Frontend (.env.production) + +```env +NEXT_PUBLIC_API_URL=https://autonetsellcar.com +``` + +--- + +## Verification Commands + +### 1. DB ์—ฐ๊ฒฐ ํ™•์ธ + +```bash +ssh server2 "docker exec autonet-backend python -c \"from app.config import get_settings; print('DB_NAME:', get_settings().DB_NAME)\"" +# ์˜ˆ์ƒ ์ถœ๋ ฅ: DB_NAME: autonet +``` + +### 2. API ๋ฐ์ดํ„ฐ ํ™•์ธ + +```bash +curl -s https://autonetsellcar.com/api/hero-banners/ | jq 'length' +# ์˜ˆ์ƒ ์ถœ๋ ฅ: 8 ์ด์ƒ (0์ด๋ฉด ๋ฌธ์ œ!) +``` + +### 3. ์ด๋ฏธ์ง€ ํ™•์ธ + +```bash +curl -I https://autonetsellcar.com/uploads/cars/1/image_0.jpg +# ์˜ˆ์ƒ ์ถœ๋ ฅ: HTTP/2 200 +``` + +### 4. Frontend API URL ํ™•์ธ + +```bash +ssh server2 "docker exec autonet-frontend grep -o 'autonetsellcar.com' /app/.next/static/chunks/app/**/*.js | head -1" +# ์˜ˆ์ƒ ์ถœ๋ ฅ: autonetsellcar.com (localhost๋‚˜ 192.168.0.202๊ฐ€ ์•„๋‹˜!) +``` + +--- + +## Quick Reference Card + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ PRODUCTION VALUES โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ DB_NAME: autonet (NOT mongolcar!) โ”‚ +โ”‚ DB_PASSWORD: roskfl@1122 (@ needs URL encoding) โ”‚ +โ”‚ API_URL: https://autonetsellcar.com (NOT http://...) โ”‚ +โ”‚ UPLOADS: /opt/autonet/production/backend/uploads โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## Change History + +| ๋‚ ์งœ | ๋ณ€๊ฒฝ ๋‚ด์šฉ | +|------|----------| +| 2026-01-02 | ๋ฌธ์„œ ์ตœ์ดˆ ์ƒ์„ฑ (๋ฐ˜๋ณต ๋ฐฐํฌ ์˜ค๋ฅ˜ ๋ฐฉ์ง€ ๋ชฉ์ ) | + +--- + +**์ด ๋ฌธ์„œ์˜ ๊ฐ’๊ณผ ๋‹ค๋ฅด๋ฉด ๋ฌด์กฐ๊ฑด ํ‹€๋ฆฐ ๊ฒƒ์ž…๋‹ˆ๋‹ค!** diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 4edee9a..aac7018 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -233,9 +233,162 @@ docker exec autonet-frontend sh -c "grep -r 'localhost:8000' /app/.next/static/c --- +--- + +## 2026-01-02: ๋ฐ˜๋ณต๋˜๋Š” ๋ฐฐํฌ ์˜ค๋ฅ˜ ์ข…ํ•ฉ (CRITICAL) + +### ๋ฌธ์ œ 1: DB_NAME ์˜ค๋ฅ˜ (`mongolcar` vs `autonet`) + +**์ฆ์ƒ**: +- ์‚ฌ์ดํŠธ์— ์ฐจ๋Ÿ‰/๋ฐฐ๋„ˆ ์—†์Œ +- API๊ฐ€ ๋นˆ ๋ฐฐ์—ด `[]` ๋ฐ˜ํ™˜ +- ๋กœ๊ทธ์ธ์€ ๋˜์ง€๋งŒ ๋ฐ์ดํ„ฐ ์—†์Œ + +**์›์ธ**: +- ์ˆ˜๋™ `docker run` ์‹œ DB_NAME์„ `mongolcar`๋กœ ์ž˜๋ชป ์ž…๋ ฅ +- PostgreSQL์— `mongolcar`์™€ `autonet` ๋‘ DB๊ฐ€ ์กด์žฌ +- `mongolcar`๋Š” ๋น„์–ด์žˆ๊ณ , ์‹ค์ œ ๋ฐ์ดํ„ฐ๋Š” `autonet`์— ์žˆ์Œ + +**ํ™•์ธ ๋ฐฉ๋ฒ•**: +```bash +# ํ˜„์žฌ ์—ฐ๊ฒฐ๋œ DB ํ™•์ธ +ssh server2 "docker exec autonet-backend python -c \"from app.config import get_settings; print('DB_NAME:', get_settings().DB_NAME)\"" + +# DB๋ณ„ ๋ฐ์ดํ„ฐ ํ™•์ธ +ssh server1 "docker exec postgres-primary psql -U admin -d autonet -c 'SELECT COUNT(*) FROM cars;'" +ssh server1 "docker exec postgres-primary psql -U admin -d mongolcar -c 'SELECT COUNT(*) FROM cars;'" +``` + +**ํ•ด๊ฒฐ**: +- `DB_NAME=autonet` ํ™•์ธ ํ›„ ์ปจํ…Œ์ด๋„ˆ ์žฌ์‹œ์ž‘ +- CLAUDE.md์˜ ํ‘œ์ค€ ๋ช…๋ น์–ด ๋ณต์‚ฌํ•ด์„œ ์‚ฌ์šฉ + +--- + +### ๋ฌธ์ œ 2: ๋น„๋ฐ€๋ฒˆํ˜ธ `@` ํŒŒ์‹ฑ ์˜ค๋ฅ˜ + +**์ฆ์ƒ**: +- `could not translate host name "1122@192.168.0.201"` +- ๋ฐฑ์—”๋“œ ์ปจํ…Œ์ด๋„ˆ ๊ณ„์† ์žฌ์‹œ์ž‘ + +**์›์ธ**: +- `DB_PASSWORD=roskfl@1122`์˜ `@`๊ฐ€ URL์—์„œ ํ˜ธ์ŠคํŠธ ๊ตฌ๋ถ„์ž๋กœ ์ธ์‹๋จ +- `config.py`์— `quote_plus()` URL ์ธ์ฝ”๋”ฉ์ด ์—†๊ฑฐ๋‚˜, Docker ์ด๋ฏธ์ง€๊ฐ€ ์˜ค๋ž˜๋จ + +**ํ™•์ธ ๋ฐฉ๋ฒ•**: +```bash +# config.py์— quote_plus ์žˆ๋Š”์ง€ ํ™•์ธ +ssh server2 "docker exec autonet-backend cat /app/app/config.py | grep quote_plus" +``` + +**ํ•ด๊ฒฐ**: +1. `config.py`์— `quote_plus()` ์ ์šฉ ํ™•์ธ +2. Docker ์ด๋ฏธ์ง€ ์žฌ๋นŒ๋“œ: `docker build --no-cache -t production-backend ./backend` + +--- + +### ๋ฌธ์ œ 3: ์ด๋ฏธ์ง€ 404 (์ž˜๋ชป๋œ uploads ๊ฒฝ๋กœ) + +**์ฆ์ƒ**: +- `/uploads/cars/40/image_0.jpg` โ†’ 404 +- Hero ๋ฐฐ๋„ˆ์— ๊ธฐ๋ณธ ์ด๋ฏธ์ง€๋งŒ ํ‘œ์‹œ + +**์›์ธ**: +- Production ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ž˜๋ชป๋œ ๊ฒฝ๋กœ ๋งˆ์šดํŠธ: `/home/damon/mongolcar/data/uploads` (๋น„์–ด์žˆ์Œ!) +- ์‹ค์ œ ์ด๋ฏธ์ง€๋Š” `/opt/autonet/production/backend/uploads`์— ์žˆ์Œ + +**ํ™•์ธ ๋ฐฉ๋ฒ•**: +```bash +# ์˜ฌ๋ฐ”๋ฅธ ๊ฒฝ๋กœ (ํŒŒ์ผ ์žˆ์–ด์•ผ ํ•จ) +ssh server2 "ls /opt/autonet/production/backend/uploads/cars/ | head -5" + +# ์ž˜๋ชป๋œ ๊ฒฝ๋กœ (๋น„์–ด์žˆ์Œ!) +ssh server2 "ls /home/damon/mongolcar/data/uploads/cars/ 2>/dev/null || echo 'Empty or not exists'" +``` + +**ํ•ด๊ฒฐ**: +- ๋ณผ๋ฅจ ๋งˆ์šดํŠธ๋ฅผ `-v /opt/autonet/production/backend/uploads:/app/uploads`๋กœ ๋ณ€๊ฒฝ +- CLAUDE.md์˜ ํ‘œ์ค€ ๋ช…๋ น์–ด ์‚ฌ์šฉ + +--- + +### ๋ฌธ์ œ 4: Mixed Content (HTTPS/HTTP) + +**์ฆ์ƒ**: +- ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†”์— Mixed Content ์˜ค๋ฅ˜ +- API ์š”์ฒญ ์‹คํŒจ +- ์‚ฌ์ดํŠธ์— ๊ธฐ๋ณธ ์ด๋ฏธ์ง€๋งŒ ํ‘œ์‹œ + +**์›์ธ**: +- Frontend๊ฐ€ `NEXT_PUBLIC_API_URL=http://192.168.0.202:8000`์œผ๋กœ ๋นŒ๋“œ๋จ +- HTTPS ํŽ˜์ด์ง€์—์„œ HTTP API ํ˜ธ์ถœ ์‹œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฐจ๋‹จ + +**ํ™•์ธ ๋ฐฉ๋ฒ•**: +```bash +# ๋นŒ๋“œ๋œ JS์—์„œ API URL ํ™•์ธ +ssh server2 "docker exec autonet-frontend grep -r '192.168.0.202:8000' /app/.next/static/chunks/ | head -1" +``` + +**ํ•ด๊ฒฐ**: +```bash +# Frontend ๋นŒ๋“œ ์ „ ํ•„์ˆ˜! +ssh server2 "echo 'NEXT_PUBLIC_API_URL=https://autonetsellcar.com' > /home/damon/mongolcar/frontend/.env.production" +ssh server2 "cd /home/damon/mongolcar/frontend && docker build --no-cache -t production-frontend ." +``` + +--- + +### ๋ฌธ์ œ 5: .env ํŒŒ์ผ์ด ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ƒ์„ฑ๋จ + +**์ฆ์ƒ**: +- ๋กœ๊ทธ์ธ ์‹คํŒจ +- `(sqlite3.OperationalError) no such column` ์—๋Ÿฌ +- ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๋กœ๋“œ ์‹คํŒจ + +**์›์ธ**: +- docker-compose ๋ณผ๋ฅจ ๋งˆ์šดํŠธ ์‹œ `.env` ํŒŒ์ผ์ด ์—†์œผ๋ฉด ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ƒ์„ฑ๋จ + +**ํ™•์ธ ๋ฐฉ๋ฒ•**: +```bash +ssh server2 "ls -la /opt/autonet/production/backend/.env" +# 'd'๋กœ ์‹œ์ž‘ํ•˜๋ฉด ๋””๋ ‰ํ† ๋ฆฌ (๋ฌธ์ œ!) +``` + +**ํ•ด๊ฒฐ**: +```bash +# ๋””๋ ‰ํ† ๋ฆฌ๋ฉด ์‚ญ์ œ ํ›„ ํŒŒ์ผ๋กœ ์žฌ์ƒ์„ฑ +ssh server2 "rm -rf /opt/autonet/production/backend/.env" +ssh server2 "cat > /opt/autonet/production/backend/.env << 'EOF' +USE_SQLITE=False +DB_HOST=192.168.0.201 +DB_PORT=5432 +DB_NAME=autonet +DB_USER=admin +DB_PASSWORD=roskfl@1122 +EOF" +``` + +--- + +## ์žฌ๋ฐœ ๋ฐฉ์ง€ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +๋ฐฐํฌ ์‹œ ๋ฐ˜๋“œ์‹œ ํ™•์ธ: + +- [ ] DB_NAME์ด `autonet`์ธ๊ฐ€? (์ ˆ๋Œ€ `mongolcar` ์•„๋‹˜!) +- [ ] Uploads ๊ฒฝ๋กœ๊ฐ€ `/opt/autonet/production/backend/uploads`์ธ๊ฐ€? +- [ ] Frontend `.env.production`์— `https://autonetsellcar.com`์ด ์„ค์ •๋˜์–ด ์žˆ๋Š”๊ฐ€? +- [ ] `--no-cache`๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋นŒ๋“œํ–ˆ๋Š”๊ฐ€? +- [ ] ๋ฐฐํฌ ํ›„ `curl https://autonetsellcar.com/api/hero-banners/`๋กœ ๋ฐ์ดํ„ฐ ํ™•์ธํ–ˆ๋Š”๊ฐ€? + +--- + ## ๋ณ€๊ฒฝ ์ด๋ ฅ | ๋‚ ์งœ | ๋ฌธ์ œ | ํ•ด๊ฒฐ | |------|------|------| +| 2026-01-02 | DB_NAME ์˜ค๋ฅ˜ (mongolcar vs autonet) | DB_NAME=autonet ํ™•์ธ, ํ‘œ์ค€ ๋ช…๋ น์–ด ์‚ฌ์šฉ | +| 2026-01-02 | ๋น„๋ฐ€๋ฒˆํ˜ธ @ ํŒŒ์‹ฑ ์˜ค๋ฅ˜ | quote_plus() URL ์ธ์ฝ”๋”ฉ ์ ์šฉ | +| 2026-01-02 | ์ด๋ฏธ์ง€ 404 (์ž˜๋ชป๋œ uploads ๊ฒฝ๋กœ) | /opt/autonet/production/backend/uploads ์‚ฌ์šฉ | +| 2026-01-02 | Mixed Content ์˜ค๋ฅ˜ | NEXT_PUBLIC_API_URL=https://autonetsellcar.com | | 2024-12-30 | ์šด์˜์„œ๋ฒ„ ์ด๋ฏธ์ง€ ๋ฏธํ‘œ์‹œ (๋ฐฐ๋„ˆ) | ์†Œ์Šค ์ฝ”๋“œ ๋™๊ธฐํ™” + Docker ์žฌ๋นŒ๋“œ | | 2024-12-30 | ์ฐจ๋Ÿ‰ ์ƒ์„ธ ์ด๋ฏธ์ง€ ๋ฏธํ‘œ์‹œ | `getImageUrl()` ํ•จ์ˆ˜ ์ˆ˜์ • | diff --git a/backend/app/api/vehicle_requests.py b/backend/app/api/vehicle_requests.py index 337bf76..ba580a7 100644 --- a/backend/app/api/vehicle_requests.py +++ b/backend/app/api/vehicle_requests.py @@ -4,7 +4,7 @@ from typing import List from datetime import datetime, timedelta from ..database import get_db -from ..models import VehicleRequest, RequestVehicle, PurchasedVehicle, User, DealerInfo, SystemSettings +from ..models import VehicleRequest, RequestVehicle, PurchasedVehicle, User, DealerInfo, SystemSettings, Car from ..schemas import ( VehicleRequestCreate, VehicleRequestResponse, RequestVehicleCreate, RequestVehicleResponse, RequestVehicleApprove, @@ -100,9 +100,21 @@ def get_my_requests( else: approved_vehicles = [] + # Enrich approved vehicles with latest soldout status from cars table + enriched_vehicles = [] + for v in approved_vehicles: + vehicle_response = RequestVehicleResponse.model_validate(v) + # Get latest soldout status from cars table + if v.car_id: + car = db.query(Car).filter(Car.id == v.car_id).first() + if car: + # Add soldout status to car_data + vehicle_response.car_data = {**vehicle_response.car_data, "soldout": car.soldout} + enriched_vehicles.append(vehicle_response) + result.append(VehicleRequestWithVehicles( request=VehicleRequestResponse.model_validate(req), - approved_vehicles=[RequestVehicleResponse.model_validate(v) for v in approved_vehicles] + approved_vehicles=enriched_vehicles )) return result @@ -189,9 +201,30 @@ def admin_get_request_detail( if not request: raise HTTPException(status_code=404, detail="Request not found") + # Import CarPerformanceCheck for PDF status + from ..models import CarPerformanceCheck + + # Enrich with PDF status and soldout + enriched_vehicles = [] + for v in request.recommended_vehicles: + vehicle_response = RequestVehicleResponse.model_validate(v) + + # Get PDF status and soldout from car + if v.car_id: + car = db.query(Car).filter(Car.id == v.car_id).first() + perf_check = db.query(CarPerformanceCheck).filter(CarPerformanceCheck.car_id == v.car_id).first() + + vehicle_response.car_data = { + **vehicle_response.car_data, + "soldout": car.soldout if car else False, + "has_pdf": bool(perf_check and perf_check.pdf_path), + "check_num": perf_check.check_number if perf_check else None, + } + enriched_vehicles.append(vehicle_response) + return VehicleRequestWithVehicles( request=VehicleRequestResponse.model_validate(request), - approved_vehicles=[RequestVehicleResponse.model_validate(v) for v in request.recommended_vehicles] + approved_vehicles=enriched_vehicles ) @@ -202,7 +235,7 @@ def admin_add_vehicle( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): - """Admin: Add a vehicle to a request""" + """Admin: Add a vehicle to a request (also imports to cars table)""" if not current_user.is_admin: raise HTTPException(status_code=403, detail="Admin access required") @@ -210,9 +243,51 @@ def admin_add_vehicle( if not request: raise HTTPException(status_code=404, detail="Request not found") + # Extract car data + car_data = vehicle_data.car_data + source_id = str(car_data.get("id", "")) + + # Check if car already exists in cars table + existing_car = None + if source_id: + existing_car = db.query(Car).filter( + Car.source == "carmodoo", + Car.source_id == source_id + ).first() + + car_id = None + if existing_car: + car_id = existing_car.id + elif source_id: + # Create new car record from car_data + new_car = Car( + source="carmodoo", + source_id=source_id, + car_name=car_data.get("car_name", ""), + year=car_data.get("year"), + mileage=car_data.get("mileage"), + price_krw=car_data.get("original_price"), + fuel=car_data.get("fuel"), + transmission=car_data.get("transmission"), + color=car_data.get("color"), + displacement=car_data.get("displacement"), + margin_krw=car_data.get("korea_margin"), + margin_mn=car_data.get("mongolia_margin"), + check_num=car_data.get("check_num"), + is_displayed=True, # Displayed so user can view recommended car + status="active" + ) + db.add(new_car) + db.flush() + car_id = new_car.id + + # Update car_data with local car_id for frontend + car_data["local_car_id"] = car_id + vehicle = RequestVehicle( request_id=request_id, - car_data=vehicle_data.car_data, + car_id=car_id, + car_data=car_data, is_approved=vehicle_data.is_approved, approved_at=datetime.utcnow() if vehicle_data.is_approved else None ) diff --git a/frontend/src/app/admin/cars/page.tsx b/frontend/src/app/admin/cars/page.tsx index d72a270..5b015cc 100644 --- a/frontend/src/app/admin/cars/page.tsx +++ b/frontend/src/app/admin/cars/page.tsx @@ -218,6 +218,17 @@ export default function CarsAdminPage() { added: number; errors: number; } | null>(null); + const [requestInfo, setRequestInfo] = useState<{ + user_email?: string; + user_name?: string; + maker_name?: string; + model_name?: string; + grade_name?: string; + year_from?: number; + year_to?: number; + mileage_max?: number; + fuel?: string; + } | null>(null); // Dealer description editing state const [showDescEditModal, setShowDescEditModal] = useState(false); @@ -299,6 +310,21 @@ export default function CarsAdminPage() { // Switch to Carmodoo tab setActiveTab('carmodoo'); + + // Fetch request details for display + vehicleRequestsApi.adminGetRequestDetail(parseInt(requestId)).then(data => { + setRequestInfo({ + user_email: data.request.user_email, + user_name: data.request.user_name, + maker_name: data.request.maker_name, + model_name: data.request.model_name, + grade_name: data.request.grade_name, + year_from: data.request.year_from, + year_to: data.request.year_to, + mileage_max: data.request.mileage_max, + fuel: data.request.fuel, + }); + }).catch(err => console.error('Failed to load request info:', err)); } }, [requestId]); @@ -1195,24 +1221,76 @@ export default function CarsAdminPage() { {/* Request Mode Banner */} {requestId && ( -
-
-
- - - -
-
-

Adding vehicles to Request #{requestId}

-

Search and select vehicles, then click "Add to Request" button.

+
+
+
+
+ + + +
+
+

+ Adding vehicles to Request #{requestId} + {requestInfo?.user_email && ( + + ({requestInfo.user_email}) + + )} +

+
+
- + {requestInfo && ( +
+ {requestInfo.maker_name && ( +
+ Maker: + {requestInfo.maker_name} +
+ )} + {requestInfo.model_name && ( +
+ Model: + {requestInfo.model_name} +
+ )} + {requestInfo.grade_name && ( +
+ Grade: + {requestInfo.grade_name} +
+ )} + {(requestInfo.year_from || requestInfo.year_to) && ( +
+ Year: + + {requestInfo.year_from || '-'} ~ {requestInfo.year_to || '-'} + +
+ )} + {requestInfo.mileage_max && ( +
+ Mileage: + + ~{Math.round(requestInfo.mileage_max / 10000)}๋งŒkm + +
+ )} + {requestInfo.fuel && ( +
+ Fuel: + {requestInfo.fuel} +
+ )} +
+ )}
)} @@ -2568,6 +2646,16 @@ export default function CarsAdminPage() { {selectedCar.status}
+
+ Sold Out + + {selectedCar.soldout ? 'SOLD OUT' : 'Available'} + +
Seize Count 0 ? 'text-red-600' : ''}`}> diff --git a/frontend/src/app/cars/[id]/page.tsx b/frontend/src/app/cars/[id]/page.tsx index 6ea927f..b7d7ccd 100644 --- a/frontend/src/app/cars/[id]/page.tsx +++ b/frontend/src/app/cars/[id]/page.tsx @@ -410,6 +410,15 @@ export default function CarDetailPage() {

)} + + {/* Soldout overlay */} + {car.soldout && ( +
+ + {language === 'ko' ? 'ํŒ๋งค์™„๋ฃŒ' : 'SOLD OUT'} + +
+ )}
{/* Thumbnails */} @@ -527,9 +536,16 @@ export default function CarDetailPage() { {/* Details */}
-

- {translate(car.car_name) || `${translate(car.maker?.name) || ''} ${translate(car.model?.name) || ''}`.trim() || '-'} -

+
+

+ {translate(car.car_name) || `${translate(car.maker?.name) || ''} ${translate(car.model?.name) || ''}`.trim() || '-'} +

+ {car.soldout && ( + + {language === 'ko' ? 'ํŒ๋งค์™„๋ฃŒ' : 'SOLD OUT'} + + )} +
{(() => { const price = formatPrice(car.final_price_krw || car.price_krw); diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index f2306be..514ea5c 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -48,6 +48,7 @@ export interface Car { dealer_description_mn?: string; dealer_description_ru?: string; status: string; + soldout?: boolean; created_at: string; updated_at: string; maker?: CarMaker;