Skip to content

Commit 1230961

Browse files
committed
Implement comprehensive API configuration system - replace hardcoded URLs with environment-aware configuration
1 parent 732d526 commit 1230961

File tree

5 files changed

+268
-76
lines changed

5 files changed

+268
-76
lines changed

.github/workflows/deploy.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: "pages"
15+
cancel-in-progress: false
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
24+
- name: Setup Node
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: '18'
28+
cache: 'npm'
29+
30+
- name: Install dependencies
31+
run: npm ci
32+
33+
- name: Build with environment variables
34+
env:
35+
VITE_API_BASE_URL: https://dummyjson.com
36+
run: npm run build
37+
38+
- name: Setup Pages
39+
uses: actions/configure-pages@v4
40+
41+
- name: Upload artifact
42+
uses: actions/upload-pages-artifact@v3
43+
with:
44+
path: './dist'
45+
46+
deploy:
47+
environment:
48+
name: github-pages
49+
url: ${{ steps.deployment.outputs.page_url }}
50+
runs-on: ubuntu-latest
51+
needs: build
52+
steps:
53+
- name: Deploy to GitHub Pages
54+
id: deployment
55+
uses: actions/deploy-pages@v4

src/entities/post/api/PostAPI.ts

Lines changed: 34 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,28 @@
1-
import { ApiClient } from "../../../shared/api/api"
1+
import { ApiService } from "../../../shared/api/services"
22
import { Post, PostItem, CreatePost, UpdatePost, Tag } from "../model/types"
33

44
/**
5-
* PostAPI 클래스는 ApiClient를 상속받아 기본 경로를 설정하고,
6-
* 각 메서드에 대한 요청을 처리합니다.
5+
* PostAPI 클래스는 ApiService를 사용하여 게시글 관련 API 요청을 처리합니다.
76
*/
8-
class PostAPI extends ApiClient {
7+
class PostAPI extends ApiService {
98
constructor() {
109
super("/posts")
1110
}
1211

1312
/**
1413
* 게시글 목록 조회
15-
* @param limit - 한 페이지에 표시할 게시글 수
16-
* @param skip - 건너뛸 게시글 수
17-
* @param sortBy - 정렬 기준
18-
* @param order - 정렬 순서
19-
* @returns 게시글 목록
2014
*/
21-
async getPosts(limit: number, skip: number, sortBy?: string, order?: string): Promise<Post> {
22-
const params = new URLSearchParams()
23-
params.append("limit", limit.toString())
24-
params.append("skip", skip.toString())
25-
if (sortBy) params.append("sortBy", sortBy)
26-
if (order) params.append("order", order)
27-
28-
return await this.get(`?${params.toString()}`)
15+
async getPosts(
16+
limit: number,
17+
skip: number,
18+
sortBy?: string,
19+
order?: string
20+
): Promise<Post> {
21+
return this.get<Post>("", { limit, skip, sortBy, order })
2922
}
3023

3124
/**
3225
* 검색어로 게시글 목록 조회
33-
* @param searchQuery - 검색어
34-
* @param limit - 한 페이지에 표시할 게시글 수
35-
* @param skip - 건너뛸 게시글 수
36-
* @param sortBy - 정렬 기준
37-
* @param order - 정렬 순서
38-
* @returns 검색 결과
3926
*/
4027
async getPostsBySearch(
4128
searchQuery: string,
@@ -44,69 +31,59 @@ class PostAPI extends ApiClient {
4431
sortBy?: string,
4532
order?: string,
4633
): Promise<Post> {
47-
const params = new URLSearchParams()
48-
params.append("q", searchQuery)
49-
params.append("limit", limit.toString())
50-
params.append("skip", skip.toString())
51-
if (sortBy) params.append("sortBy", sortBy)
52-
if (order) params.append("order", order)
53-
54-
return await this.get(`/search?${params.toString()}`)
34+
return this.get<Post>("/search", {
35+
q: searchQuery,
36+
limit,
37+
skip,
38+
sortBy,
39+
order
40+
})
5541
}
5642

5743
/**
5844
* 태그 목록 조회
59-
* @returns 태그 목록
6045
*/
6146
async getTags(): Promise<Tag[]> {
62-
return await this.get("/tags")
47+
return this.get<Tag[]>("/tags")
6348
}
6449

6550
/**
6651
* 태그로 게시글 목록 조회
67-
* @param tag - 태그
68-
* @param limit - 한 페이지에 표시할 게시글 수
69-
* @param skip - 건너뛸 게시글 수
70-
* @param sortBy - 정렬 기준
71-
* @param order - 정렬 순서
72-
* @returns 태그 결과
7352
*/
74-
async getPostsByTag(tag: string, limit: number, skip: number, sortBy?: string, order?: string): Promise<Post> {
75-
const params = new URLSearchParams()
76-
params.append("limit", limit.toString())
77-
params.append("skip", skip.toString())
78-
if (sortBy) params.append("sortBy", sortBy)
79-
if (order) params.append("order", order)
80-
81-
return await this.get(`/tag/${tag}?${params.toString()}`)
53+
async getPostsByTag(
54+
tag: string,
55+
limit: number,
56+
skip: number,
57+
sortBy?: string,
58+
order?: string
59+
): Promise<Post> {
60+
return this.get<Post>(`/tag/${tag}`, {
61+
limit,
62+
skip,
63+
sortBy,
64+
order
65+
})
8266
}
8367

8468
/**
8569
* 게시글 추가
86-
* @param post - 추가할 게시글 정보
87-
* @returns 추가된 게시글 정보
8870
*/
8971
async createPost(post: CreatePost): Promise<PostItem> {
90-
return await this.post("/add", post)
72+
return this.post<PostItem, CreatePost>("/add", post)
9173
}
9274

9375
/**
9476
* 게시글 업데이트
95-
* @param id - 게시글 ID
96-
* @param post - 수정할 게시글 정보
97-
* @returns 수정된 게시글 정보
9877
*/
9978
async updatePost(id: number, post: Partial<UpdatePost>): Promise<PostItem> {
100-
return await this.put(`/${id}`, post)
79+
return this.put<PostItem, Partial<UpdatePost>>(`/${id}`, post)
10180
}
10281

10382
/**
10483
* 게시글 삭제
105-
* @param id - 게시글 ID
106-
* @returns 삭제 성공 여부
10784
*/
10885
async deletePost(id: number): Promise<void> {
109-
return await this.delete(`/${id}`)
86+
return this.delete<void>(`/${id}`)
11087
}
11188
}
11289

src/shared/api/api.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
// GitHub Pages 배포 환경 감지
2-
const isGitHubPages = window.location.hostname === 'amelia-shin.github.io'
1+
import { getApiConfig, buildApiUrl } from './config'
32

4-
// GitHub Pages에서는 전체 URL, 개발환경에서는 상대 경로
5-
const BASE_URL = isGitHubPages ? "https://dummyjson.com" : ""
3+
// API 설정 가져오기
4+
const apiConfig = getApiConfig()
65

76
export const api = {
8-
get: <T>(path: string, init?: RequestInit) =>
9-
fetch(`${BASE_URL}${path}`, { ...init }).then((r) => r.json() as Promise<T>),
7+
get: <T>(path: string, init?: RequestInit) => {
8+
const url = buildApiUrl(path, apiConfig)
9+
return fetch(url, { ...init }).then((r) => r.json() as Promise<T>)
10+
},
1011
// post/put/delete 등...
1112
}
1213

@@ -45,19 +46,7 @@ export class ApiClient {
4546
* @returns 전체 URL
4647
*/
4748
private buildUrl(path: string): string {
48-
// GitHub Pages 배포 환경 감지
49-
const isGitHubPages = window.location.hostname === 'amelia-shin.github.io'
50-
51-
// GitHub Pages에서는 전체 URL 사용
52-
if (isGitHubPages) {
53-
return `https://dummyjson.com${this.basePath}${path}`
54-
}
55-
56-
// 개발 환경에서는 상대 경로 사용
57-
if (this.basePath.startsWith("/api")) {
58-
return this.basePath + path
59-
}
60-
return "/api" + this.basePath + path
49+
return buildApiUrl(`${this.basePath}${path}`, apiConfig)
6150
}
6251

6352
/**

src/shared/api/config.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* API 설정 관리
3+
* 환경별로 다른 API 엔드포인트를 동적으로 설정
4+
*/
5+
6+
export interface ApiConfig {
7+
baseUrl: string
8+
useProxy: boolean
9+
timeout: number
10+
}
11+
12+
/**
13+
* 환경별 API 설정을 가져옵니다
14+
*/
15+
export function getApiConfig(): ApiConfig {
16+
// 1. 환경 변수 우선 (가장 명시적)
17+
if (import.meta.env.VITE_API_BASE_URL) {
18+
return {
19+
baseUrl: import.meta.env.VITE_API_BASE_URL,
20+
useProxy: false,
21+
timeout: 10000
22+
}
23+
}
24+
25+
// 2. 런타임 환경 감지
26+
const hostname = window.location.hostname
27+
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1'
28+
const isGitHubPages = hostname.includes('github.io')
29+
const isVercel = hostname.includes('vercel.app')
30+
const isNetlify = hostname.includes('netlify.app')
31+
32+
// 3. 환경별 기본 설정
33+
if (isLocalhost) {
34+
// 로컬 개발 환경: Vite proxy 사용
35+
return {
36+
baseUrl: '',
37+
useProxy: true,
38+
timeout: 10000
39+
}
40+
}
41+
42+
if (isGitHubPages || isVercel || isNetlify) {
43+
// 정적 호스팅 환경: 실제 API 서버 사용
44+
return {
45+
baseUrl: 'https://dummyjson.com',
46+
useProxy: false,
47+
timeout: 15000
48+
}
49+
}
50+
51+
// 4. 기본값 (fallback)
52+
return {
53+
baseUrl: '',
54+
useProxy: true,
55+
timeout: 10000
56+
}
57+
}
58+
59+
/**
60+
* API URL을 생성합니다
61+
*/
62+
export function buildApiUrl(path: string, config: ApiConfig): string {
63+
if (config.useProxy) {
64+
// 프록시 사용 (개발 환경)
65+
return path.startsWith('/api') ? path : `/api${path}`
66+
}
67+
68+
// 전체 URL 사용 (프로덕션 환경)
69+
const cleanPath = path.startsWith('/') ? path : `/${path}`
70+
return `${config.baseUrl}${cleanPath}`
71+
}

0 commit comments

Comments
 (0)