배포스크립트
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -20,8 +20,8 @@ dist/
|
|||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
lib/
|
/lib/
|
||||||
lib64/
|
/lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
|||||||
274
deploy/NAS-SETUP-GUIDE.md
Normal file
274
deploy/NAS-SETUP-GUIDE.md
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
# Auto-Stock NAS 배포 가이드 (Synology DS1517)
|
||||||
|
|
||||||
|
## 환경 정보
|
||||||
|
- NAS: Synology DS1517+ (16GB RAM)
|
||||||
|
- OS: DSM 7.x (Linux 3.10.108, x86_64)
|
||||||
|
- Docker: 24.0.2
|
||||||
|
- Docker Compose: v2.20.1 (호스트) / v2.20.3 (Jenkins 내부)
|
||||||
|
- Cloudflare Tunnel 사용 중
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 디렉토리 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
/volume1/docker/
|
||||||
|
├── autostock/ # 앱 배포 디렉토리
|
||||||
|
│ ├── .env # 환경변수 (Git에 없음, 수동 관리)
|
||||||
|
│ ├── docker-compose.yml
|
||||||
|
│ ├── docker/
|
||||||
|
│ ├── backend/
|
||||||
|
│ ├── mcp-server/
|
||||||
|
│ ├── frontend/
|
||||||
|
│ ├── logs/
|
||||||
|
│ └── backups/
|
||||||
|
├── gitea/ # Gitea 데이터
|
||||||
|
├── jenkins/ # Jenkins 데이터
|
||||||
|
└── cicd/ # CI/CD 인프라 설정
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 초기 설정
|
||||||
|
|
||||||
|
### 2.1 디렉토리 생성
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /volume1/docker/autostock
|
||||||
|
sudo mkdir -p /volume1/docker/gitea
|
||||||
|
sudo mkdir -p /volume1/docker/jenkins
|
||||||
|
sudo chown -R shuim:users /volume1/docker/autostock /volume1/docker/gitea
|
||||||
|
sudo chown -R 1000:1000 /volume1/docker/jenkins
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Gitea 설치
|
||||||
|
```bash
|
||||||
|
docker run -d --name gitea --restart unless-stopped -p 23000:3000 -v /volume1/docker/gitea:/data -e TZ=Asia/Seoul -e "GITEA__server__ROOT_URL=https://gitea.shium.net" gitea/gitea:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
- Cloudflare Tunnel: `gitea.shium.net:443 → localhost:23000`
|
||||||
|
- 초기 설정: https://gitea.shium.net 접속 → SQLite3 선택 → 관리자 생성
|
||||||
|
- 저장소 생성: `auto-stock` (Private)
|
||||||
|
|
||||||
|
### 2.3 Jenkins 설치
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d --name jenkins --restart unless-stopped -p 21080:8080 -p 21000:50000 -v /volume1/docker/jenkins:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v /volume1/docker/autostock:/volume1/docker/autostock -e TZ=Asia/Seoul -e JAVA_OPTS="-Xmx512m -Xms256m" --user root jenkins/jenkins:lts-jdk17
|
||||||
|
```
|
||||||
|
|
||||||
|
**중요**: `-v /volume1/docker/autostock:/volume1/docker/autostock` 마운트 필수 (Jenkins에서 배포 디렉토리 접근)
|
||||||
|
|
||||||
|
초기 비밀번호 확인:
|
||||||
|
```bash
|
||||||
|
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Jenkins 컨테이너 내부 도구 설치
|
||||||
|
|
||||||
|
**Python3 + rsync + curl:**
|
||||||
|
```bash
|
||||||
|
docker exec -u root jenkins bash -c "apt-get update && apt-get install -y python3 rsync curl"
|
||||||
|
```
|
||||||
|
- `systemd` 관련 에러 발생하지만 무시 가능 (Python3는 정상 설치됨)
|
||||||
|
|
||||||
|
**Docker CLI (바이너리 직접 설치):**
|
||||||
|
```bash
|
||||||
|
docker exec -u root jenkins bash -c "curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz -o /tmp/docker.tgz && tar xz -C /tmp -f /tmp/docker.tgz && cp /tmp/docker/docker /usr/local/bin/docker && chmod +x /usr/local/bin/docker && rm -rf /tmp/docker /tmp/docker.tgz && docker --version"
|
||||||
|
```
|
||||||
|
- `docker.io` apt 패키지는 Synology 커널과 호환되지 않음 (systemd 의존성 충돌)
|
||||||
|
- 바이너리 직접 설치로 해결
|
||||||
|
|
||||||
|
**Docker Compose (독립 바이너리):**
|
||||||
|
```bash
|
||||||
|
docker exec -u root jenkins bash -c "curl -fsSL -o /tmp/dc https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-linux-x86_64 && mv /tmp/dc /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose && docker-compose version"
|
||||||
|
```
|
||||||
|
- NAS 호스트의 `docker-compose`를 `docker cp`로 복사하면 GLIBC 버전 불일치 발생
|
||||||
|
- GitHub에서 독립 바이너리 다운로드로 해결
|
||||||
|
- `curl -o /usr/local/bin/docker-compose` 직접 쓰면 `client returned ERROR on write` 발생 → `/tmp/dc`에 먼저 받고 `mv`로 해결
|
||||||
|
|
||||||
|
**Docker 소켓 권한:**
|
||||||
|
```bash
|
||||||
|
docker exec -u root jenkins bash -c "chmod 666 /var/run/docker.sock"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 설치 검증
|
||||||
|
```bash
|
||||||
|
docker exec jenkins bash -c "python3 --version && docker --version && docker-compose version"
|
||||||
|
```
|
||||||
|
예상 출력:
|
||||||
|
```
|
||||||
|
Python 3.13.x
|
||||||
|
Docker version 24.0.7, build afdd53b
|
||||||
|
Docker Compose version v2.20.3
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Jenkins 파이프라인 설정
|
||||||
|
|
||||||
|
### 3.1 Pipeline 프로젝트 생성
|
||||||
|
1. Jenkins 대시보드 → **+ 새로운 Item**
|
||||||
|
2. 이름: `auto-stock`, 타입: **Pipeline** → OK
|
||||||
|
|
||||||
|
### 3.2 Pipeline 설정 (구성 페이지)
|
||||||
|
1. Pipeline 섹션 → Definition: **Pipeline script from SCM**
|
||||||
|
2. SCM: **Git**
|
||||||
|
3. Repository URL: `https://gitea.shium.net/shuim/auto-stock.git`
|
||||||
|
4. Credentials: **Add** → Username/Password (Gitea 계정)
|
||||||
|
5. Branch: `*/main`
|
||||||
|
6. Script Path: `Jenkinsfile`
|
||||||
|
7. **Lightweight checkout: 반드시 체크 해제** (체크 시 `fatal: not in a git directory` 에러)
|
||||||
|
8. 저장
|
||||||
|
|
||||||
|
### 3.3 Gitea Webhook (자동 빌드)
|
||||||
|
Gitea 저장소 → Settings → Webhooks → Add Webhook:
|
||||||
|
- URL: `http://localhost:21080/gitea-webhook/post`
|
||||||
|
- Content Type: `application/json`
|
||||||
|
- Trigger: Push Events
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 로컬 Git 설정
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/commylife/myproject/java/workspace/auto-stock
|
||||||
|
git remote add nas https://gitea.shium.net/shuim/auto-stock.git
|
||||||
|
git push nas main
|
||||||
|
```
|
||||||
|
|
||||||
|
배포 흐름:
|
||||||
|
```
|
||||||
|
로컬 코드 수정 → git push nas main → Jenkins 자동 빌드 → NAS 배포
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. .env 설정
|
||||||
|
|
||||||
|
`/volume1/docker/autostock/.env` 파일을 수동 생성하고 비밀번호를 설정:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# NAS SSH 접속 후
|
||||||
|
nano /volume1/docker/autostock/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
필수 변경 항목:
|
||||||
|
- `POSTGRES_PASSWORD`: 강력한 비밀번호
|
||||||
|
- `REDIS_PASSWORD`: 강력한 비밀번호
|
||||||
|
- `SECRET_KEY`: 64자 이상 랜덤 문자열
|
||||||
|
- `API_KEY`, `MCP_API_KEY`: API 인증키
|
||||||
|
- `ANTHROPIC_API_KEY`: Claude API 키
|
||||||
|
- `KIS_*`: 한국투자증권 API 키
|
||||||
|
- `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`: 텔레그램 알림
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 수동 첫 배포
|
||||||
|
|
||||||
|
Jenkins 파이프라인이 .env 부재로 실패할 경우:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /volume1/docker/autostock
|
||||||
|
# .env 파일이 준비된 상태에서
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 트러블슈팅
|
||||||
|
|
||||||
|
### 에러: `python3: not found`
|
||||||
|
**원인**: Jenkins 컨테이너에 Python 미설치
|
||||||
|
**해결**:
|
||||||
|
```bash
|
||||||
|
docker exec -u root jenkins bash -c "apt-get update && apt-get install -y python3"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 에러: `docker.io` 설치 시 systemd dpkg 에러
|
||||||
|
**원인**: Synology 커널(3.10.108)이 최신 systemd와 호환되지 않음
|
||||||
|
**해결**: `docker.io` 패키지 대신 Docker CLI 바이너리 직접 설치
|
||||||
|
```bash
|
||||||
|
docker exec -u root jenkins bash -c "curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz -o /tmp/docker.tgz && tar xz -C /tmp -f /tmp/docker.tgz && cp /tmp/docker/docker /usr/local/bin/docker && chmod +x /usr/local/bin/docker && rm -rf /tmp/docker /tmp/docker.tgz"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 에러: `unknown shorthand flag: 'f' in -f` (docker compose)
|
||||||
|
**원인**: Jenkins 내부 Docker CLI에 `compose` 플러그인이 없음. `docker compose`(v2 플러그인)가 아닌 `docker-compose`(독립 바이너리) 사용 필요
|
||||||
|
**해결**: docker-compose 독립 바이너리 설치 (섹션 2.4 참조)
|
||||||
|
|
||||||
|
### 에러: `docker-compose: not found`
|
||||||
|
**원인**: NAS 호스트의 docker-compose를 `docker cp`로 복사하면 GLIBC 버전 불일치
|
||||||
|
**해결**: GitHub에서 독립 바이너리 직접 다운로드
|
||||||
|
```bash
|
||||||
|
docker exec -u root jenkins bash -c "curl -fsSL -o /tmp/dc https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-linux-x86_64 && mv /tmp/dc /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 에러: `curl: (23) client returned ERROR on write of 1369 bytes`
|
||||||
|
**원인**: `/usr/local/bin/`에 직접 쓸 때 권한/공간 문제
|
||||||
|
**해결**: `/tmp`에 먼저 다운로드 후 `mv`로 이동
|
||||||
|
|
||||||
|
### 에러: `fatal: not in a git directory`
|
||||||
|
**원인**: Pipeline 설정에서 **Lightweight checkout**이 활성화되어 있으면 git workspace 없이 Jenkinsfile을 읽으려 함
|
||||||
|
**해결**: Pipeline 구성 → **Lightweight checkout 체크 해제** → 저장
|
||||||
|
|
||||||
|
### 에러: `mkdir: cannot create directory '/volume1': Permission denied`
|
||||||
|
**원인**: Jenkins 컨테이너에 NAS 볼륨이 마운트되지 않음
|
||||||
|
**해결**: Jenkins 컨테이너 재생성 시 `-v /volume1/docker/autostock:/volume1/docker/autostock` 볼륨 마운트 추가
|
||||||
|
|
||||||
|
### 에러: Gitea 500 Internal Server Error
|
||||||
|
**원인**: 이전 설정 데이터가 꼬진 상태
|
||||||
|
**해결**:
|
||||||
|
```bash
|
||||||
|
docker rm -f gitea
|
||||||
|
sudo rm -rf /volume1/docker/gitea/*
|
||||||
|
# 다시 docker run으로 재생성
|
||||||
|
```
|
||||||
|
|
||||||
|
### 에러: `git push nas main` → `Failed to connect to port 3001`
|
||||||
|
**원인**: Cloudflare Tunnel 사용 시 직접 포트 접근 불가
|
||||||
|
**해결**: HTTPS URL 사용
|
||||||
|
```bash
|
||||||
|
git remote remove nas
|
||||||
|
git remote add nas https://gitea.shium.net/shuim/auto-stock.git
|
||||||
|
git push nas main
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Jenkins 컨테이너 재시작 후 복구
|
||||||
|
|
||||||
|
Jenkins 컨테이너가 재시작되면 바이너리 설치된 Docker CLI와 docker-compose가 유지됩니다 (볼륨 아님, 컨테이너 레이어에 저장). 단, 컨테이너를 **삭제 후 재생성**하면 재설치 필요:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 전체 재설치 (컨테이너 삭제 후 재생성 시)
|
||||||
|
docker exec -u root jenkins bash -c "apt-get update && apt-get install -y python3 rsync curl && curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz -o /tmp/docker.tgz && tar xz -C /tmp -f /tmp/docker.tgz && cp /tmp/docker/docker /usr/local/bin/docker && chmod +x /usr/local/bin/docker && rm -rf /tmp/docker /tmp/docker.tgz && curl -fsSL -o /tmp/dc https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-linux-x86_64 && mv /tmp/dc /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose && chmod 666 /var/run/docker.sock"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 메모리 배분 (docker-compose.yml)
|
||||||
|
|
||||||
|
| 서비스 | 메모리 제한 |
|
||||||
|
|--------|-----------|
|
||||||
|
| PostgreSQL | 1GB |
|
||||||
|
| Backend | 768MB |
|
||||||
|
| RabbitMQ | 512MB |
|
||||||
|
| Redis | 256MB (maxmemory 200MB) |
|
||||||
|
| MCP Server | 256MB |
|
||||||
|
| Frontend (Nginx) | 64MB |
|
||||||
|
| Gitea | ~200MB |
|
||||||
|
| Jenkins | ~1GB (빌드 시) |
|
||||||
|
| **합계** | ~4.1GB / 16GB |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Cloudflare Tunnel 라우팅
|
||||||
|
|
||||||
|
| 도메인 | 포트 | 용도 |
|
||||||
|
|--------|------|------|
|
||||||
|
| gitea.shium.net:443 | localhost:23000 | Gitea (Git 웹 UI) |
|
||||||
|
| jenkins.shium.net:443 | localhost:21080 | Jenkins (CI/CD) |
|
||||||
|
| stock.shium.net:443 | localhost:3000 | Frontend |
|
||||||
|
| stock.shium.net/api | localhost:8000 | Backend API |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*마지막 업데이트: 2026-03-27*
|
||||||
179
frontend/src/lib/api.ts
Normal file
179
frontend/src/lib/api.ts
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
import axios, { AxiosError, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
|
||||||
|
import { config } from '../config'
|
||||||
|
|
||||||
|
// 재시도 설정
|
||||||
|
interface RetryConfig extends InternalAxiosRequestConfig {
|
||||||
|
retryCount?: number
|
||||||
|
retryMaxCount?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 오프라인 상태 추적
|
||||||
|
let isOffline = false
|
||||||
|
|
||||||
|
// 온/오프라인 이벤트 리스너 등록
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
isOffline = false
|
||||||
|
console.log('✅ Connection restored')
|
||||||
|
showOfflineIndicator(false)
|
||||||
|
})
|
||||||
|
window.addEventListener('offline', () => {
|
||||||
|
isOffline = true
|
||||||
|
console.log('❌ Connection lost')
|
||||||
|
showOfflineIndicator(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 오프라인 표시기 UI 업데이트
|
||||||
|
function showOfflineIndicator(offline: boolean) {
|
||||||
|
// 오프라인 배너 표시 (필요시 구현)
|
||||||
|
if (offline) {
|
||||||
|
console.warn('⚠️ You are offline. Some features may not work properly.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 재시도 가능 여부 판단
|
||||||
|
function isRetryableError(error: AxiosError): boolean {
|
||||||
|
if (!error.response) {
|
||||||
|
// 네트워크 오류 (재시도 가능)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = error.response.status
|
||||||
|
// 429 (Too Many Requests), 500+, 503 (Service Unavailable) 재시도 가능
|
||||||
|
return status === 429 || status >= 500
|
||||||
|
}
|
||||||
|
|
||||||
|
// 지수 백오프 계산
|
||||||
|
function getBackoffDelay(retryCount: number): number {
|
||||||
|
// 1000ms, 2000ms, 4000ms (3회 재시도)
|
||||||
|
return Math.min(1000 * Math.pow(2, retryCount), 8000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: config.apiUrl,
|
||||||
|
timeout: 15000, // 기본 타임아웃 15초
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 요청 인터셉터 - 토큰 추가 및 재시도 카운트 초기화
|
||||||
|
api.interceptors.request.use(
|
||||||
|
(config: RetryConfig) => {
|
||||||
|
// API 키가 있으면 X-API-Key 헤더에 추가
|
||||||
|
const apiKey = localStorage.getItem('api_key')
|
||||||
|
if (apiKey && config.headers) {
|
||||||
|
config.headers['X-API-Key'] = apiKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// 재시도 카운트 초기화 (첫 요청인 경우)
|
||||||
|
if (!config.retryCount) {
|
||||||
|
config.retryCount = 0
|
||||||
|
config.retryMaxCount = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(error: AxiosError) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 응답 인터셉터 - 글로벌 에러 처리 및 재시도 로직
|
||||||
|
api.interceptors.response.use(
|
||||||
|
(response: AxiosResponse) => response,
|
||||||
|
async (error: AxiosError) => {
|
||||||
|
const config = error.config as RetryConfig
|
||||||
|
|
||||||
|
// 재시도 설정이 없으면 기본값 설정
|
||||||
|
if (!config) {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.retryCount) {
|
||||||
|
config.retryCount = 0
|
||||||
|
config.retryMaxCount = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// 재시도 가능 여부 확인
|
||||||
|
const maxCount = config.retryMaxCount ?? 3
|
||||||
|
const shouldRetry =
|
||||||
|
isRetryableError(error) &&
|
||||||
|
config.retryCount < maxCount
|
||||||
|
|
||||||
|
if (shouldRetry) {
|
||||||
|
config.retryCount += 1
|
||||||
|
const delay = getBackoffDelay(config.retryCount - 1)
|
||||||
|
console.log(
|
||||||
|
`🔄 Retrying request (${config.retryCount}/${config.retryMaxCount}) ` +
|
||||||
|
`after ${delay}ms: ${config.method?.toUpperCase()} ${config.url}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 지수 백오프로 재시도
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, delay))
|
||||||
|
return api(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 에러 상태코드별 처리
|
||||||
|
if (error.response) {
|
||||||
|
const status = error.response.status
|
||||||
|
const message = (error.response.data as any)?.message || error.message
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 401:
|
||||||
|
// 인증 실패 - API 키 제거 후 새로고침 (로그인 화면으로)
|
||||||
|
console.warn('Authentication failed:', config.url)
|
||||||
|
localStorage.removeItem('api_key')
|
||||||
|
window.location.reload()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 403:
|
||||||
|
// 권한 부족
|
||||||
|
console.error('🚫 Permission denied:', message)
|
||||||
|
// 필요시 권한 오류 토스트 표시
|
||||||
|
break
|
||||||
|
|
||||||
|
case 429:
|
||||||
|
// 요청 제한
|
||||||
|
console.warn('⏱️ Rate limited. Please try again later.')
|
||||||
|
const retryAfter = error.response.headers['retry-after']
|
||||||
|
if (retryAfter) {
|
||||||
|
console.log(`Retry after: ${retryAfter}s`)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 500:
|
||||||
|
case 502:
|
||||||
|
case 503:
|
||||||
|
case 504:
|
||||||
|
// 서버 오류 (재시도 완료됨)
|
||||||
|
console.error(`❌ Server error (${status}): ${message}`)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.error(`❌ Error (${status}): ${message}`)
|
||||||
|
}
|
||||||
|
} else if (error.request) {
|
||||||
|
// 요청은 보내졌지만 응답을 받지 못함 (네트워크 오류)
|
||||||
|
if (isOffline) {
|
||||||
|
console.error('❌ No response from server. You appear to be offline.')
|
||||||
|
} else {
|
||||||
|
console.error('❌ No response from server. Network error or server is unreachable.')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 요청 설정 중 오류
|
||||||
|
console.error('❌ Error setting up request:', error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const getUsStockPrice = (ticker: string) =>
|
||||||
|
api.get(`/market-data/overseas/price/${ticker}`)
|
||||||
|
|
||||||
|
export const getUsStockChart = (ticker: string, period: string = 'D') =>
|
||||||
|
api.get(`/market-data/overseas/chart/${ticker}`, { params: { period } })
|
||||||
|
|
||||||
|
export default api
|
||||||
Reference in New Issue
Block a user