Skip to content

Commit 7789c22

Browse files
committed
Merge branch 'feat/front_yyr' of https://github.com/MyHomeCatch/MyHomeCatch_Front into feat/front_yyr
2 parents c35c3af + 4f722ea commit 7789c22

35 files changed

Lines changed: 2704 additions & 973 deletions

.github/workflows/deploy.yml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
name: Fullstack CI/CD Deploy to EC2
2+
3+
on:
4+
push:
5+
branches: ['main'] # 배포 브랜치
6+
7+
jobs:
8+
build-and-deploy:
9+
runs-on: amazonlinux-2023
10+
11+
steps:
12+
# 1. 코드 체크아웃
13+
- name: Checkout repository
14+
uses: actions/checkout@v4
15+
16+
# 2. JDK 17 설치 (백엔드용)
17+
- name: Set up Java
18+
uses: actions/setup-java@v4
19+
with:
20+
distribution: 'temurin'
21+
java-version: '17'
22+
23+
# 3. Node.js 설치 (프론트엔드용)
24+
- name: Set up Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: '18'
28+
29+
# 4. 프론트엔드 빌드 (frontend/dist 생성)
30+
- name: Build frontend
31+
working-directory: ./frontend
32+
run: |
33+
npm ci
34+
npm run build
35+
36+
# 5. 백엔드 빌드 (backend/build/libs/ROOT.war 생성)
37+
- name: Build backend
38+
working-directory: ./backend
39+
run: |
40+
chmod +x ./gradlew
41+
./gradlew clean build -x test
42+
43+
# 6. 배포 패키지 생성 (압축)
44+
- name: Create deployment package
45+
run: |
46+
# 배포에 필요한 파일들을 모을 deploy 디렉터리 생성
47+
mkdir -p deploy/nginx/conf.d
48+
mkdir -p deploy/frontend/dist
49+
mkdir -p deploy/backend/build/libs
50+
51+
# 필요한 파일들을 deploy 디렉터리로 복사
52+
cp docker-compose.yml deploy/
53+
cp nginx/conf.d/default.conf deploy/nginx/conf.d/
54+
cp -R frontend/dist/. deploy/frontend/dist/
55+
mv backend/build/libs/*.war deploy/backend/build/libs/ROOT.war
56+
57+
# deploy 디렉터리 전체를 압축
58+
tar -zcvf deploy.tar.gz -C deploy .
59+
60+
61+
# 7. EC2에 파일 전송
62+
- name: Copy deployment to EC2
63+
uses: appleboy/scp-action@master
64+
with:
65+
host: ${{ secrets.EC2_HOST }}
66+
username: ${{ secrets.EC2_USERNAME }}
67+
key: ${{ secrets.SSH_PRIVATE_KEY }}
68+
source: "deploy.tar.gz"
69+
target: "/home/${{ secrets.EC2_USERNAME }}"
70+
rm: true
71+
72+
# 8. EC2에서 Docker Compose로 배포
73+
- name: Deploy on EC2 via SSH
74+
uses: appleboy/ssh-action@master
75+
with:
76+
host: ${{ secrets.EC2_HOST }}
77+
username: ${{ secrets.EC2_USERNAME }}
78+
key: ${{ secrets.SSH_PRIVATE_KEY }}
79+
script: |
80+
cd /home/${{ secrets.EC2_USERNAME }}
81+
82+
# 배포 루트 디렉터리 설정
83+
APP_DIR="/home/${{ secrets.EC2_USERNAME }}/myhomecatch"
84+
85+
# 기존 디렉터리 삭제
86+
echo "Removing old deployment directory..."
87+
rm -rf $APP_DIR
88+
mkdir -p $APP_DIR
89+
# mkdir -p frontend/dist backend/build/libs nginx/conf.d
90+
91+
# 압축 파일 이동 및 해제
92+
echo "Extracting deployment package..."
93+
mv /home/${{ secrets.EC2_USERNAME }}/deploy.tar.gz $APP_DIR/
94+
cd $APP_DIR
95+
tar -zxvf deploy.tar.gz
96+
97+
# 컨테이너 재시작
98+
docker-compose down --remove-orphans
99+
docker-compose up -d --build
100+
101+
# 불필요 이미지 정리
102+
docker image prune -f
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Frontend CI/CD
2+
3+
# 이 워크플로가 실행될 조건: main 브랜치에 push가 발생했을 때
4+
on:
5+
push:
6+
branches: ['develop']
7+
8+
jobs:
9+
build-and-deploy:
10+
runs-on: amazonlinux-2023
11+
12+
# frontend 디렉터리에서 작업이 실행되도록 기본 경로 설정
13+
defaults:
14+
run:
15+
working-directory: ./frontend
16+
17+
steps:
18+
# 1. 코드 체크아웃
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
22+
# 2. Node.js 18.x 버전 설정
23+
- name: Set up Node.js
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: 18
27+
28+
# 3. 의존성 패키지 설치
29+
- name: Install dependencies
30+
run: npm ci
31+
32+
# 4. 프로덕션 빌드
33+
- name: Build for production
34+
run: npm run build
35+
36+
# 5. 빌드된 파일(dist)을 EC2 웹서버 루트로 전송
37+
- name: Deploy to EC2
38+
uses: appleboy/scp-action@master
39+
with:
40+
host: ${{ secrets.EC2_HOST }}
41+
username: ${{ secrets.EC2_USERNAME }}
42+
key: ${{ secrets.SSH_PRIVATE_KEY }}
43+
source: './frontend/dist/*'
44+
target: '/home/${{ secrets.EC2_USERNAME }}/frontend/dist'
45+
# strip_components: 2 # source 경로에서 'frontend/dist' 부분을 제거

src/App.vue

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,20 @@ watch(
1919
</script>
2020

2121
<template>
22-
<DefaultLayout>
23-
<RouterView />
24-
</DefaultLayout>
22+
<div id="app">
23+
<DefaultLayout>
24+
<RouterView />
25+
</DefaultLayout>
26+
</div>
2527
</template>
2628

27-
<style scoped></style>
29+
<style scoped>
30+
#app {
31+
background-image: url('@/assets/images/background1.png');
32+
background-size: cover;
33+
background-repeat: no-repeat;
34+
background-attachment: fixed;
35+
background-position: center;
36+
min-height: 100vh;
37+
}
38+
</style>

src/api/bookmardApi.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// api/bookmarkService.js
2+
import axios from 'axios';
3+
4+
const BOOKMARK_API_URL = '/api/api/bookmark';
5+
6+
// 즐겨찾기 목록 조회
7+
export const getBookmarks = async (token) => {
8+
try {
9+
const response = await axios.get(BOOKMARK_API_URL, {
10+
headers: { Authorization: `Bearer ${token}` },
11+
});
12+
return response.data;
13+
} catch (error) {
14+
console.error('즐겨찾기 목록 조회 실패:', error);
15+
throw error;
16+
}
17+
};
18+
19+
// 즐겨찾기 추가
20+
export const addBookmark = async (bookmarkData, token) => {
21+
try {
22+
const response = await axios.post(BOOKMARK_API_URL, bookmarkData, {
23+
headers: { Authorization: `Bearer ${token}` },
24+
});
25+
return response.data;
26+
} catch (error) {
27+
console.error('즐겨찾기 추가 실패:', error);
28+
throw error;
29+
}
30+
};
31+
32+
// 즐겨찾기 삭제
33+
export const removeBookmark = async (bookmarkData, token) => {
34+
try {
35+
const response = await axios.delete(BOOKMARK_API_URL, {
36+
data: bookmarkData,
37+
headers: { Authorization: `Bearer ${token}` },
38+
});
39+
return response.data;
40+
} catch (error) {
41+
console.error('즐겨찾기 삭제 실패:', error);
42+
throw error;
43+
}
44+
};
45+
46+
// 즐겨찾기 토글 (추가/삭제 자동 판별)
47+
export const toggleBookmark = async (
48+
danziId,
49+
userId,
50+
token,
51+
currentFavorites
52+
) => {
53+
const bookmarkData = { userId, danziId };
54+
const isCurrentlyFavorited = currentFavorites.some(
55+
(fav) => fav.danziId === danziId
56+
);
57+
58+
try {
59+
if (isCurrentlyFavorited) {
60+
return await removeBookmark(bookmarkData, token);
61+
} else {
62+
return await addBookmark(bookmarkData, token);
63+
}
64+
} catch (error) {
65+
console.error('즐겨찾기 토글 실패:', error);
66+
throw error;
67+
}
68+
};

src/api/bookmarkApi.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import axios from 'axios';
2+
import { setupInterceptors } from './commonApi';
3+
4+
const api = axios.create({
5+
baseURL: 'http://localhost:8080/api',
6+
headers: {
7+
'Content-Type': 'application/json',
8+
},
9+
withCredentials: true, // 쿠키를 포함하여 요청
10+
});
11+
12+
// 인터셉터 설정
13+
setupInterceptors(api);
14+
15+
export default {
16+
/**
17+
* 북마크를 추가합니다.
18+
* @param {object} bookmarkData - { userId, danziId }
19+
*/
20+
async createBookmark(bookmarkData) {
21+
if (!bookmarkData.userId || !bookmarkData.danziId) {
22+
return Promise.reject(
23+
new Error('userId 또는 danziId가 제공되지 않았습니다.')
24+
);
25+
}
26+
// [수정] const로 requestBody 변수를 선언합니다.
27+
const requestBody = {
28+
userId: bookmarkData.userId,
29+
danziId: bookmarkData.danziId,
30+
};
31+
console.log('북마크 추가 요청 본문:', requestBody);
32+
const { data } = await api.post('/bookmark', requestBody);
33+
return data;
34+
},
35+
36+
/**
37+
* 북마크를 삭제합니다.
38+
* @param {object} bookmarkData - { userId, danziId }
39+
*/
40+
async deleteBookmark(bookmarkData) {
41+
if (!bookmarkData.userId || !bookmarkData.danziId) {
42+
return Promise.reject(
43+
new Error('userId 또는 danziId가 제공되지 않았습니다.')
44+
);
45+
}
46+
// [수정] const로 requestBody 변수를 선언합니다.
47+
const requestBody = {
48+
userId: bookmarkData.userId,
49+
danziId: bookmarkData.danziId,
50+
};
51+
console.log('북마크 삭제 요청 본문:', requestBody);
52+
// axios.delete 요청 시 body는 { data: ... } 객체로 감싸서 보내야 합니다.
53+
const { data } = await api.delete('/bookmark', { data: requestBody });
54+
return data;
55+
},
56+
};

src/api/detailPageApi.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import axios from 'axios';
22
import { setupInterceptors } from './commonApi';
33

4-
// 새 axios 인스턴스 생성
54
const detailApi = axios.create({
65
baseURL: 'http://localhost:8080/api', // API 기본 경로
6+
headers: {
7+
'Content-Type': 'application/json',
8+
},
9+
withCredentials: true, // 쿠키를 포함하여 요청
710
});
811

912
// 인터셉터 설정
@@ -14,10 +17,53 @@ setupInterceptors(detailApi);
1417
* @param {string | number} danziId - 단지의 고유 ID
1518
* @returns {Promise<object>} - 주택 상세 정보
1619
*/
17-
export const getHouseDetailById = (danziId) => {
20+
export const getHouseDetailById = (danziId, selfCheckResult = null) => {
1821
if (!danziId) {
1922
return Promise.reject(new Error('danziId가 제공되지 않았습니다.'));
2023
}
2124
// API 명세에 따라 /house/{danziId} 형태로 요청
2225
return detailApi.get(`/house/${danziId}`);
2326
};
27+
28+
/**
29+
* 특정 ID의 houseCard 가져오는 함수
30+
* @param {string | number} danziId - 단지의 고유 ID
31+
* @returns {Promise<object>} - 단지의 houseCard
32+
*/
33+
export const getHouseCardById = (danziId) => {
34+
if (!danziId) {
35+
return Promise.reject(new Error('danziId가 제공되지 않았습니다.'));
36+
}
37+
// API 명세에 따라 /house/card/{danziId} 형태로 요청
38+
return detailApi.get(`/house/card/${danziId}`);
39+
};
40+
41+
export const getHouseDetailByIdWithSelfCheck = async (
42+
userId,
43+
selfCheckResult,
44+
danziId
45+
) => {
46+
if (!danziId) {
47+
return Promise.reject(new Error('danziId가 제공되지 않았습니다.'));
48+
}
49+
try {
50+
// API 명세에 따라 /house/{danziId} 형태로 요청
51+
const requestBody = {
52+
userId: userId, // 사용자 ID
53+
selfCheckResult: selfCheckResult,
54+
};
55+
console.log('API 요청 본문:', requestBody);
56+
// API 요청
57+
return detailApi.post(`/house/${danziId}`, requestBody);
58+
} catch (error) {
59+
console.error('주택 상세 정보 가져오기 실패:', error);
60+
throw error;
61+
}
62+
};
63+
64+
export const getBookmarksByHouseId = (houseId) => {
65+
if (!houseId) {
66+
return Promise.reject(new Error('houseId가 제공되지 않았습니다.'));
67+
}
68+
return detailApi.get(`/bookmark/${houseId}`);
69+
};

src/api/selfCheck.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,9 @@ export default {
124124
return data;
125125
},
126126

127+
// 진단 내용
128+
async getSelfCheckResult(){
129+
const { data } = await api.get('/self-check/results');
130+
return data;
131+
}
127132
};

src/components/DefaultLayout.vue

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import Footer from './Footer.vue';
88
<div class="app-wrapper">
99
<Header />
1010
<div class="navbar-sticky">
11-
<div class="container">
1211
<NavBar />
13-
</div>
1412
</div>
1513

1614
<div class="container">

0 commit comments

Comments
 (0)