From 718c5b0474c798d59232eef78946e2842d2b6f46 Mon Sep 17 00:00:00 2001 From: AutonetSellCar Deploy Date: Sat, 3 Jan 2026 16:22:34 +0900 Subject: [PATCH] Add staging environment documentation and minor fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Document staging vs production DB separation (autonet_staging) - Add staging sync and deployment commands to CLAUDE.md - Update changelog with 2025-01-03 changes ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 46 ++++++ backend/app/api/carmodoo.py | 11 +- frontend/package.json | 4 +- .../src/app/admin/vehicle-requests/page.tsx | 137 +++++++++++++----- frontend/src/app/profile/page.tsx | 16 +- 5 files changed, 163 insertions(+), 51 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4357b12..b4451e1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -134,6 +134,50 @@ ssh server2 "cd /opt/autonet/production/frontend && docker build --no-cache -t p --- +## 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`): +```env +DB_NAME=autonet_staging # Production์€ autonet +``` + +**docker-compose.staging.yml**: +- Frontend: `NEXT_PUBLIC_API_URL=https://staging.autonetsellcar.com` (build arg) +- Backend: `.env` ํŒŒ์ผ์„ ๋ณผ๋ฅจ ๋งˆ์šดํŠธ๋กœ ์—ฐ๊ฒฐ + +### ์†Œ์Šค ์ฝ”๋“œ ๋™๊ธฐํ™” + +**โš ๏ธ ์ค‘์š”**: Staging ๋””๋ ‰ํ† ๋ฆฌ๋Š” Git ์ €์žฅ์†Œ๊ฐ€ ์•„๋‹˜! Production๊ณผ ์ˆ˜๋™ ๋™๊ธฐํ™” ํ•„์š” + +```bash +# Production โ†’ Staging ์†Œ์Šค ๋™๊ธฐํ™” +ssh damon@192.168.0.202 "rsync -av --exclude='node_modules' --exclude='.next' /opt/autonet/production/frontend/ /opt/autonet/staging/frontend/" + +# Staging Frontend ์žฌ๋นŒ๋“œ +ssh damon@192.168.0.202 "cd /opt/autonet/staging && docker compose -f docker-compose.staging.yml build --no-cache frontend-staging && docker compose -f docker-compose.staging.yml up -d frontend-staging" +``` + +### Staging DB ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ๋ณต์‚ฌ + +```bash +# 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 +``` + +--- + ## 1. ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ``` @@ -790,6 +834,8 @@ Import ์‹œ ์ž๋™ ๋ฒˆ์—ญ (Azure API) | ๋‚ ์งœ | ๋ณ€๊ฒฝ ๋‚ด์šฉ | |------|----------| +| 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 ์นด๋“œ ์ถ”๊ฐ€ | diff --git a/backend/app/api/carmodoo.py b/backend/app/api/carmodoo.py index 9d84f9f..97068d9 100644 --- a/backend/app/api/carmodoo.py +++ b/backend/app/api/carmodoo.py @@ -16,7 +16,7 @@ from pathlib import Path from lxml import etree from lxml import html as lxml_html from ..database import get_db -from ..models import Car, CarMaker, CarModel, CarImage, CarOption, CarPerformanceCheck, CarSpecification, PerformanceCheckView, CarView, User +from ..models import Car, CarMaker, CarModel, CarImage, CarOption, CarPerformanceCheck, CarSpecification, PerformanceCheckView, CarView, User, VehicleRequest, RequestVehicle from ..models.settings import SystemSettings from ..api.auth import get_current_user, get_current_user_optional, get_current_admin_user from ..services.cache_service import CacheService @@ -1712,7 +1712,14 @@ async def get_car_performance_check( CarView.car_id == car_id ).first() - has_access = (existing_perf_view is not None) or (existing_car_view is not None) + # Check 3: This car was recommended to the user (paid 1 CC for recommendation) + recommended_vehicle = db.query(RequestVehicle).join(VehicleRequest).filter( + VehicleRequest.user_id == current_user.id, + RequestVehicle.car_id == car_id, + RequestVehicle.is_approved == True + ).first() + + has_access = (existing_perf_view is not None) or (existing_car_view is not None) or (recommended_vehicle is not None) # If no access, return only basic info (that performance check exists) if not has_access: diff --git a/frontend/package.json b/frontend/package.json index a1f186a..ece3980 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,9 @@ "dev": "next dev", "build": "next build", "start": "next start -p 3000", - "lint": "next lint" + "lint": "next lint", + "audit": "npm audit --omit=dev", + "audit:fix": "npm audit fix --omit=dev" }, "dependencies": { "@heroicons/react": "^2.1.1", diff --git a/frontend/src/app/admin/vehicle-requests/page.tsx b/frontend/src/app/admin/vehicle-requests/page.tsx index cc6b7fb..cc3ce50 100644 --- a/frontend/src/app/admin/vehicle-requests/page.tsx +++ b/frontend/src/app/admin/vehicle-requests/page.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import Link from 'next/link'; -import { vehicleRequestsApi, VehicleRequest, VehicleRequestWithVehicles } from '@/lib/api'; +import { vehicleRequestsApi, VehicleRequest, VehicleRequestWithVehicles, adminPdfApi } from '@/lib/api'; export default function AdminVehicleRequestsPage() { const [requests, setRequests] = useState([]); @@ -10,6 +10,7 @@ export default function AdminVehicleRequestsPage() { const [isLoading, setIsLoading] = useState(true); const [statusFilter, setStatusFilter] = useState(''); const [deletingVehicleId, setDeletingVehicleId] = useState(null); + const [regeneratingPdfId, setRegeneratingPdfId] = useState(null); // Load requests useEffect(() => { @@ -89,6 +90,23 @@ export default function AdminVehicleRequestsPage() { } }; + // Regenerate PDF for a car + const regeneratePdf = async (carId: number) => { + try { + setRegeneratingPdfId(carId); + await adminPdfApi.regenerateSingle(carId); + // Reload request detail to refresh PDF status + if (selectedRequest) { + await loadRequestDetail(selectedRequest.request.id); + } + } catch (error) { + console.error('Failed to regenerate PDF:', error); + alert('Failed to regenerate PDF. Please try again.'); + } finally { + setRegeneratingPdfId(null); + } + }; + // Format date const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('ko-KR', { @@ -304,42 +322,89 @@ export default function AdminVehicleRequestsPage() { ) : (
- {selectedRequest.approved_vehicles.map((vehicle) => ( -
- {vehicle.car_data.main_image && ( - - )} -
-

{vehicle.car_data.car_name}

-

- {vehicle.car_data.year} | {vehicle.car_data.mileage?.toLocaleString()}km -

+ {selectedRequest.approved_vehicles.map((vehicle) => { + const hasPdf = vehicle.car_data.has_pdf === true; + const isSoldout = vehicle.car_data.soldout === true; + const checkNum = vehicle.car_data.check_num; + + return ( +
+ {vehicle.car_data.main_image && ( +
+ + {isSoldout && ( + SOLD + )} +
+ )} +
+

{vehicle.car_data.car_name}

+

+ {vehicle.car_data.year} | {vehicle.car_data.mileage?.toLocaleString()}km +

+ {/* PDF Status */} +
+ {hasPdf ? ( + + + + + PDF OK + + ) : ( + + + + + No PDF + {vehicle.car_id && checkNum && ( + + )} + {!checkNum && (no check_num)} + + )} +
+
+
+

+ {vehicle.car_data.final_price?.toLocaleString()}๋งŒ์› +

+ +
-
-

- {vehicle.car_data.final_price?.toLocaleString()}๋งŒ์› -

- -
-
- ))} + ); + })}
)}
diff --git a/frontend/src/app/profile/page.tsx b/frontend/src/app/profile/page.tsx index 61ac44f..f3d2561 100644 --- a/frontend/src/app/profile/page.tsx +++ b/frontend/src/app/profile/page.tsx @@ -297,21 +297,13 @@ export default function ProfilePage() {

- {/* Danger Zone - Account Withdrawal */} -
-

- {language === 'ko' ? '๊ณ„์ • ํƒˆํ‡ด' : 'Account Withdrawal'} -

-

- {language === 'ko' - ? '๊ณ„์ •์„ ํƒˆํ‡ดํ•˜๋ฉด ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค. ์ด ์ž‘์—…์€ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.' - : 'Once you withdraw your account, all your data will be deleted. This action cannot be undone.'} -

+ {/* Account Withdrawal - Subtle link */} +