Skip to content

Commit 8bfecd1

Browse files
authored
Merge pull request #83 from SWYP-mingling/chore/SW-91-add-subway-data
Chore/sw 91 add subway data
2 parents e2e973d + 15b5840 commit 8bfecd1

48 files changed

Lines changed: 4846 additions & 314 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ jobs:
4141
with:
4242
context: .
4343
push: true
44-
tags: ${{ secrets.NCR_ENDPOINT }}/mingling-app:latest
44+
tags: ${{ secrets.NCR_ENDPOINT }}/mingling-app:prod
4545
cache-from: type=gha
4646
cache-to: type=gha,mode=max
4747

4848
# 5. NCP 서버 접속 및 앱 교체
4949
- name: SSH Deploy & Health Check
5050
uses: appleboy/ssh-action@master
5151
with:
52-
host: ${{ secrets.NCP_SERVER_IP }}
52+
host: ${{ secrets.PROD_NCP_SERVER_IP }}
5353
username: root
5454
key: ${{ secrets.SSH_PRIVATE_KEY }}
5555
script: |
@@ -58,17 +58,19 @@ jobs:
5858
docker stop spring-app || true
5959
docker rm spring-app || true
6060
61-
docker pull ${{ secrets.NCR_ENDPOINT }}/mingling-app:latest
61+
docker pull ${{ secrets.NCR_ENDPOINT }}/mingling-app:prod
6262
6363
docker run -d --name spring-app \
6464
-p 8080:8080 \
6565
-e SPRING_PROFILES_ACTIVE=prod \
66-
-e SPRING_DATASOURCE_URL="${{ secrets.DB_URL }}" \
67-
-e SPRING_DATASOURCE_USERNAME="${{ secrets.DB_USERNAME }}" \
68-
-e SPRING_DATASOURCE_PASSWORD="${{ secrets.DB_PASSWORD }}" \
69-
-e SPRING_DATA_REDIS_HOST="${{ secrets.REDIS_HOST }}" \
70-
-e SPRING_SERVER_URL="${{ secrets.SPRING_SERVER_URL }}" \
71-
${{ secrets.NCR_ENDPOINT }}/mingling-app:latest
66+
-e SPRING_DATASOURCE_URL="${{ secrets.PROD_DB_URL }}" \
67+
-e SPRING_DATASOURCE_USERNAME="${{ secrets.PROD_DB_USERNAME }}" \
68+
-e SPRING_DATASOURCE_PASSWORD="${{ secrets.PROD_DB_PASSWORD }}" \
69+
-e SPRING_DATA_REDIS_HOST="${{ secrets.PROD_REDIS_HOST }}" \
70+
-e SPRING_SERVER_URL="${{ secrets.PROD_SPRING_SERVER_URL }}" \
71+
-e KAKAO_REST_API_KEY="${{ secrets.KAKAO_REST_API_KEY }}" \
72+
-e SEOUL_METRO_API_KEY="${{ secrets.SEOUL_METRO_API_KEY }}" \
73+
${{ secrets.NCR_ENDPOINT }}/mingling-app:prod
7274
7375
echo "Health check starting..."
7476
for i in {1..20}; do

.github/workflows/deploy-test.yml

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: Test Deploy to NCP
2+
3+
on:
4+
push:
5+
branches: [ "develop" ]
6+
7+
jobs:
8+
deploy:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v3
12+
13+
# 1. 빌드
14+
- name: Set up JDK 21
15+
uses: actions/setup-java@v3
16+
with:
17+
java-version: '21'
18+
distribution: 'temurin'
19+
20+
- name: Setup Gradle
21+
uses: gradle/actions/setup-gradle@v3
22+
23+
- name: Build with Gradle
24+
run: ./gradlew clean build -x test
25+
26+
# 2. Docker Buildx 설정
27+
- name: Set up Docker Buildx
28+
uses: docker/setup-buildx-action@v3
29+
30+
# 3. NCR 로그인
31+
- name: Login to NCR
32+
uses: docker/login-action@v3
33+
with:
34+
registry: ${{ secrets.NCR_ENDPOINT }}
35+
username: ${{ secrets.NCP_ACCESS_KEY }}
36+
password: ${{ secrets.NCP_SECRET_KEY }}
37+
38+
# 4. Build and Push with Cache
39+
- name: Build and Push to NCR
40+
uses: docker/build-push-action@v5
41+
with:
42+
context: .
43+
push: true
44+
tags: ${{ secrets.NCR_ENDPOINT }}/mingling-app:test
45+
cache-from: type=gha
46+
cache-to: type=gha,mode=max
47+
48+
# 5. NCP 서버 접속 및 앱 교체
49+
- name: SSH Deploy & Health Check
50+
uses: appleboy/ssh-action@master
51+
with:
52+
host: ${{ secrets.TEST_NCP_SERVER_IP }}
53+
username: root
54+
key: ${{ secrets.SSH_PRIVATE_KEY }}
55+
script: |
56+
docker login ${{ secrets.NCR_ENDPOINT }} -u ${{ secrets.NCP_ACCESS_KEY }} -p ${{ secrets.NCP_SECRET_KEY }}
57+
58+
docker stop spring-app || true
59+
docker rm spring-app || true
60+
61+
docker pull ${{ secrets.NCR_ENDPOINT }}/mingling-app:test
62+
63+
docker run -d --name spring-app \
64+
-p 8080:8080 \
65+
-e SPRING_PROFILES_ACTIVE=test \
66+
-e SPRING_DATASOURCE_URL="${{ secrets.TEST_DB_URL }}" \
67+
-e SPRING_DATASOURCE_USERNAME="${{ secrets.TEST_DB_USERNAME }}" \
68+
-e SPRING_DATASOURCE_PASSWORD="${{ secrets.TEST_DB_PASSWORD }}" \
69+
-e SPRING_DATA_REDIS_HOST="${{ secrets.TEST_REDIS_HOST }}" \
70+
-e SPRING_SERVER_URL="${{ secrets.TEST_SPRING_SERVER_URL }}" \
71+
-e KAKAO_REST_API_KEY="${{ secrets.KAKAO_REST_API_KEY }}" \
72+
-e SEOUL_METRO_API_KEY="${{ secrets.SEOUL_METRO_API_KEY }}" \
73+
${{ secrets.NCR_ENDPOINT }}/mingling-app:test
74+
75+
echo "Health check starting..."
76+
for i in {1..20}; do
77+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/actuator/health)
78+
79+
if [ $STATUS -eq 200 ]; then
80+
echo "Health check success! (Status: $STATUS)"
81+
docker image prune -f
82+
exit 0
83+
fi
84+
85+
echo "Waiting for app to start... (Status: $STATUS, Retry: $i/20)"
86+
sleep 5
87+
done
88+
89+
echo "Health check failed! Check logs."
90+
docker logs spring-app
91+
exit 1

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ COPY --from=builder app/spring-boot-loader/ ./
1111
COPY --from=builder app/snapshot-dependencies/ ./
1212
COPY --from=builder app/application/ ./
1313

14-
ENTRYPOINT ["java", "-Xmx384m", "-Dspring.profiles.active=prod", "org.springframework.boot.loader.launch.JarLauncher"]
14+
ENTRYPOINT ["java", "-Xmx384m", "org.springframework.boot.loader.launch.JarLauncher"]

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ dependencies {
5353
// Redis 관련
5454
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
5555
implementation 'org.springframework.session:spring-session-data-redis'
56+
57+
// WebClient
58+
implementation 'org.springframework.boot:spring-boot-starter-webflux'
5659
}
5760

5861
tasks.named('test') {

src/main/java/swyp/mingling/domain/meeting/controller/MeetingController.java

Lines changed: 44 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,23 @@
11
package swyp.mingling.domain.meeting.controller;
22

3-
import io.swagger.v3.oas.annotations.Parameter;
43
import io.swagger.v3.oas.annotations.tags.Tag;
5-
import jakarta.servlet.http.HttpServletRequest;
64
import jakarta.servlet.http.HttpSession;
75
import jakarta.validation.Valid;
8-
import java.util.List;
9-
import java.util.UUID;
106
import lombok.RequiredArgsConstructor;
117
import lombok.extern.slf4j.Slf4j;
12-
import org.springframework.web.bind.annotation.GetMapping;
13-
import org.springframework.web.bind.annotation.PatchMapping;
14-
import org.springframework.web.bind.annotation.PathVariable;
15-
import org.springframework.web.bind.annotation.PostMapping;
16-
import org.springframework.web.bind.annotation.RequestBody;
17-
import org.springframework.web.bind.annotation.RequestMapping;
18-
import org.springframework.web.bind.annotation.RequestParam;
19-
import org.springframework.web.bind.annotation.RestController;
20-
import org.springframework.web.bind.annotation.SessionAttribute;
8+
import org.springframework.web.bind.annotation.*;
219
import swyp.mingling.domain.meeting.dto.request.CreateDepartureRequest;
2210
import swyp.mingling.domain.meeting.dto.request.CreateMeetingRequest;
2311
import swyp.mingling.domain.meeting.dto.request.UpdateDepartureRequest;
24-
import swyp.mingling.domain.meeting.dto.response.CreateDepartureResponse;
25-
import swyp.mingling.domain.meeting.dto.response.CreateMeetingResponse;
26-
import swyp.mingling.domain.meeting.dto.response.GetMeetingStatusResponse;
27-
import swyp.mingling.domain.meeting.dto.response.GetMidpointResponse;
28-
import swyp.mingling.domain.meeting.dto.response.RecommendResponse;
29-
import swyp.mingling.domain.meeting.dto.response.ResultMeetingResponse;
30-
import swyp.mingling.domain.meeting.dto.response.UpdateDepartureResponse;
31-
import swyp.mingling.domain.meeting.service.CreateDepartureUseCase;
32-
import swyp.mingling.domain.meeting.service.CreateMeetingUseCase;
33-
import swyp.mingling.domain.meeting.service.GetMeetingStatusUseCase;
34-
import swyp.mingling.domain.meeting.service.ResultMeetingUseCase;
35-
import swyp.mingling.domain.meeting.service.UpdateDepartureUseCase;
12+
import swyp.mingling.domain.meeting.dto.response.*;
13+
import swyp.mingling.domain.meeting.dto.response.midpoint.GetMidPointResponse;
14+
import swyp.mingling.domain.meeting.service.*;
3615
import swyp.mingling.global.documentation.MeetingApiDocumentation;
3716
import swyp.mingling.global.response.ApiResponse;
3817

18+
import java.util.List;
19+
import java.util.UUID;
20+
3921
/**
4022
* 모임 관련 API 컨트롤러
4123
*/
@@ -50,7 +32,10 @@ public class MeetingController {
5032
private final ResultMeetingUseCase resultMeetingUseCase;
5133
private final CreateDepartureUseCase createDepartureUseCase;
5234
private final UpdateDepartureUseCase updateDepartureUseCase;
35+
private final DeleteDepartureUseCase deleteDepartureUseCase;
5336
private final GetMeetingStatusUseCase getMeetingStatusUseCase;
37+
private final RecommendPlaceUseCase recommendPlaceUseCase;
38+
private final MidPointAsyncUseCase midPointAsyncUseCase;
5439

5540
/**
5641
* 모임 생성 API
@@ -67,32 +52,19 @@ public ApiResponse<CreateMeetingResponse> createMeeting(
6752
}
6853

6954
/**
70-
* 중간지점 조회 API
55+
* 중간지점 조회 API (동기 버전)
7156
*
7257
* @param meetingId 모임 식별자 (UUID)
7358
* @return 중간지점 번화가 및 추천 장소 목록
7459
*/
7560
@MeetingApiDocumentation.GetMidpointDoc
7661
@GetMapping("/{meetingId}/midpoint")
77-
public ApiResponse<GetMidpointResponse> getMidpoint(@PathVariable("meetingId") UUID meetingId,
78-
@Parameter(hidden = true)
79-
@SessionAttribute(value = "userName") String userName) {
80-
// TODO: 실제 로직 구현 필요
81-
// 1. meetingId로 모임 존재 여부 확인
82-
// 2. name + meetingId로 기존 참여자 조회(확인필요)
83-
// 3. 중간 지점 이름과 위도 경도, 로그인한 참여자의 출발 위치부터 중간지점 까지의 경로 전달
84-
85-
List<GetMidpointResponse.MidpointDto> mockMidpoints = List.of(
86-
new GetMidpointResponse.MidpointDto("합정역", 37.5484757, 126.912071, 30, "2호선 > 6호선"),
87-
new GetMidpointResponse.MidpointDto("서울역", 37.554648, 126.972559, 35, "1호선 > 4호선"),
88-
new GetMidpointResponse.MidpointDto("용산역", 37.529844, 126.964804, 32, "경의중앙선 > 1호선")
89-
);
62+
public ApiResponse<Object> getMidpoint(@PathVariable("meetingId") UUID meetingId) {
9063

64+
List<GetMidPointResponse> execute = midPointAsyncUseCase.execute(meetingId);
9165

9266

93-
GetMidpointResponse response = new GetMidpointResponse(mockMidpoints);
94-
95-
return ApiResponse.success(response);
67+
return ApiResponse.success(execute);
9668
}
9769

9870
/**
@@ -102,7 +74,7 @@ public ApiResponse<GetMidpointResponse> getMidpoint(@PathVariable("meetingId") U
10274
* @return 생성된 모임 URL 응답
10375
*/
10476
@MeetingApiDocumentation.ResultMeetingDoc
105-
@GetMapping("/{meetingId}/result")
77+
@GetMapping("/result/{meetingId}")
10678
public ApiResponse<ResultMeetingResponse> resultMeeting(@PathVariable("meetingId") String meetingId) {
10779
ResultMeetingResponse response = resultMeetingUseCase.getResultMeetingUrl(meetingId);
10880
return ApiResponse.success(response);
@@ -114,20 +86,19 @@ public ApiResponse<ResultMeetingResponse> resultMeeting(@PathVariable("meetingId
11486
* @param meetingId 모임 UUID
11587
* @param midPlace 중간 지점 장소 (query param)
11688
* @param category 모임 목적 (query param)
89+
* @param page 조회할 페이지 번호 (1부터 시작)
90+
* @param size 조회할 개수 (기본값 10)
11791
* @return 장소 추천 장소들 목록
11892
*/
11993
@MeetingApiDocumentation.GetRecommendDoc
12094
@GetMapping("/{meetingId}/recommend")
121-
public ApiResponse<List<RecommendResponse>> getRecommend(@PathVariable("meetingId") UUID meetingId,
122-
@RequestParam String midPlace,
123-
@RequestParam String category) {
124-
125-
List<RecommendResponse> recommendResponses = List.of(
126-
new RecommendResponse("카페1", "서울 동작구 동작대로..."),
127-
new RecommendResponse("카페2", "서울 서초구 방배천로...")
128-
);
129-
130-
return ApiResponse.success(recommendResponses);
95+
public ApiResponse<RecommendResponse> getRecommend(@PathVariable("meetingId") UUID meetingId,
96+
@RequestParam String midPlace,
97+
@RequestParam String category,
98+
@RequestParam(defaultValue = "1") int page,
99+
@RequestParam(defaultValue = "15") int size) {
100+
RecommendResponse response = recommendPlaceUseCase.execute(midPlace, category, page, size);
101+
return ApiResponse.success(response);
131102
}
132103

133104
/**
@@ -186,5 +157,25 @@ public ApiResponse<UpdateDepartureResponse> updateDeparture(
186157
return ApiResponse.success(response);
187158

188159
}
160+
161+
/**
162+
* 출발역 삭제 API
163+
*
164+
* @return 사용자 닉네임
165+
*/
166+
@MeetingApiDocumentation.DeleteDepartDoc
167+
@DeleteMapping("/{meetingId}/departure")
168+
public ApiResponse<String> updateDeparture(
169+
@PathVariable("meetingId") UUID meetingId,
170+
HttpSession session) {
171+
172+
//세션에서 nickname 가져오기
173+
String nickname = (String) session.getAttribute(String.valueOf(meetingId));
174+
175+
String deletedNickname = deleteDepartureUseCase.execute(meetingId, nickname);
176+
177+
return ApiResponse.success(deletedNickname);
178+
179+
}
189180
}
190181

src/main/java/swyp/mingling/domain/meeting/dto/request/CreateMeetingRequest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.swagger.v3.oas.annotations.media.Schema;
44
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode;
55
import jakarta.validation.constraints.Future;
6+
import jakarta.validation.constraints.Max;
67
import jakarta.validation.constraints.Min;
78
import jakarta.validation.constraints.NotBlank;
89
import jakarta.validation.constraints.NotEmpty;
@@ -45,6 +46,7 @@ public class CreateMeetingRequest {
4546

4647
@NotNull(message = "인원은 필수입니다.")
4748
@Min(value = 2, message = "모임 인원은 최소 2명 이상이어야 합니다.")
49+
@Max(value = 10, message = "참여 인원은 10명까지 가능합니다.")
4850
@Schema(
4951
description = "모임 인원",
5052
example = "10",

src/main/java/swyp/mingling/domain/meeting/dto/response/GetMidpointResponse.java

Lines changed: 0 additions & 37 deletions
This file was deleted.

0 commit comments

Comments
 (0)