diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml index b2743203..a886cb6d 100644 --- a/.github/workflows/github-actions.yml +++ b/.github/workflows/github-actions.yml @@ -1,8 +1,8 @@ -name: CI/CD with Docker +name: CI/CD with Docker & ASG on: push: - branches: [ "deploy" ] + branches: [ "stage" ] jobs: deploy: @@ -20,7 +20,7 @@ jobs: run: | mkdir -p ./src/main/resources/firebase touch ./src/main/resources/application.yml - echo "${{ secrets.APPLICATION }}" > ./src/main/resources/application.yml + echo "${{ secrets.APPLICATION_STAGE }}" > ./src/main/resources/application.yml - name: create-json uses: jsdaniell/create-json@v1.2.2 @@ -50,16 +50,16 @@ jobs: push: true tags: ${{ secrets.DOCKER_REPO }}:latest - - name: Deploy to Server - uses: appleboy/ssh-action@master + - name: AWS credentials + uses: aws-actions/configure-aws-credentials@v2 with: - host: ${{ secrets.HOST }} - username: ${{ secrets.USERNAME }} - key: ${{ secrets.KEY }} - envs: GITHUB_SHA - script: | - echo "${{ secrets.DOCKER_COMPOSE }}" | sudo tee ./docker-compose.yml - sudo docker ps -q | xargs -r sudo docker stop && sudo docker ps -aq | xargs -r sudo docker rm - sudo docker compose -f ./docker-compose.yml down --rmi all - sudo docker pull ${{ secrets.DOCKER_REPO}}:latest - sudo docker compose -f ./docker-compose.yml up -d + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }} + aws-region: ap-northeast-2 + + - name: Auto Scaling Group Refresh + run: | + aws autoscaling start-instance-refresh \ + --auto-scaling-group-name onlyone-buddkit-back-asg \ + --strategy "Rolling" \ + --preferences "MinHealthyPercentage=50" \ No newline at end of file diff --git a/src/main/java/com/example/onlyone/domain/chat/controller/ChatRoomRestController.java b/src/main/java/com/example/onlyone/domain/chat/controller/ChatRoomRestController.java index 6781da7d..dcefafc8 100644 --- a/src/main/java/com/example/onlyone/domain/chat/controller/ChatRoomRestController.java +++ b/src/main/java/com/example/onlyone/domain/chat/controller/ChatRoomRestController.java @@ -12,7 +12,7 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/clubs/{clubId}/chat") +@RequestMapping("/api/clubs/{clubId}/chat") public class ChatRoomRestController { private final ChatRoomService chatRoomService; diff --git a/src/main/java/com/example/onlyone/domain/chat/controller/MessageRestController.java b/src/main/java/com/example/onlyone/domain/chat/controller/MessageRestController.java index fa14f9fd..ac957d67 100644 --- a/src/main/java/com/example/onlyone/domain/chat/controller/MessageRestController.java +++ b/src/main/java/com/example/onlyone/domain/chat/controller/MessageRestController.java @@ -20,7 +20,7 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/chat") +@RequestMapping("/api/chat") public class MessageRestController { private final MessageService messageService; diff --git a/src/main/java/com/example/onlyone/domain/chat/service/MessageService.java b/src/main/java/com/example/onlyone/domain/chat/service/MessageService.java index d677987e..7da4eefd 100644 --- a/src/main/java/com/example/onlyone/domain/chat/service/MessageService.java +++ b/src/main/java/com/example/onlyone/domain/chat/service/MessageService.java @@ -40,12 +40,12 @@ public class MessageService { * 메시지 저장 */ @Transactional - public ChatMessageResponse saveMessage(Long chatRoomId, Long kakaoId, String text) { + public ChatMessageResponse saveMessage(Long chatRoomId, Long userId, String text) { if (text == null || text.isBlank()) throw new CustomException(ErrorCode.MESSAGE_BAD_REQUEST); ChatRoom chatRoom = chatRoomRepository.findById(chatRoomId) .orElseThrow(() -> new CustomException(ErrorCode.CHAT_ROOM_NOT_FOUND)); - User user = userRepository.findByKakaoId(kakaoId) + User user = userRepository.findByUserId(userId) .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); // 채팅방 미참여자 차단 diff --git a/src/main/java/com/example/onlyone/domain/club/controller/ClubController.java b/src/main/java/com/example/onlyone/domain/club/controller/ClubController.java index a0b9c2dc..35d5b918 100644 --- a/src/main/java/com/example/onlyone/domain/club/controller/ClubController.java +++ b/src/main/java/com/example/onlyone/domain/club/controller/ClubController.java @@ -17,7 +17,7 @@ @RestController @Tag(name = "Club") @RequiredArgsConstructor -@RequestMapping("/clubs") +@RequestMapping("/api/clubs") public class ClubController { private final ClubService clubService; diff --git a/src/main/java/com/example/onlyone/domain/club/document/ClubDocument.java b/src/main/java/com/example/onlyone/domain/club/document/ClubDocument.java index 98fa28f7..994f1a7b 100644 --- a/src/main/java/com/example/onlyone/domain/club/document/ClubDocument.java +++ b/src/main/java/com/example/onlyone/domain/club/document/ClubDocument.java @@ -11,6 +11,8 @@ import org.springframework.data.elasticsearch.annotations.DateFormat; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + import com.fasterxml.jackson.annotation.JsonFormat; @Document(indexName = "clubs") @@ -52,10 +54,8 @@ public class ClubDocument { @Field(type = FieldType.Keyword) private String interestKoreanName; - @Field(type = FieldType.Date, - pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS||yyyy-MM-dd'T'HH:mm:ss.SSS||yyyy-MM-dd'T'HH:mm:ss") - @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss[.SSSSSS][.SSS]") - private LocalDateTime createdAt; + @Field(type = FieldType.Date, pattern = "yyyy-MM-dd'T'HH:mm:ss") + private String createdAt; @Field(type = FieldType.Text, searchAnalyzer = "club_analyzer") private String searchText; @@ -74,7 +74,7 @@ public static ClubDocument from(Club club) { .interestId(club.getInterest().getInterestId()) .interestCategory(category.name()) .interestKoreanName(category.getKoreanName()) - .createdAt(club.getCreatedAt()) + .createdAt(club.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"))) .searchText(club.getName() + " " + club.getDescription()) .build(); } diff --git a/src/main/java/com/example/onlyone/domain/club/entity/Club.java b/src/main/java/com/example/onlyone/domain/club/entity/Club.java index 11cce5b4..6769e8bd 100644 --- a/src/main/java/com/example/onlyone/domain/club/entity/Club.java +++ b/src/main/java/com/example/onlyone/domain/club/entity/Club.java @@ -98,7 +98,11 @@ public void addSchedule(Schedule schedule) { } public void incrementMemberCount() { - this.memberCount++; + if (this.memberCount == null) { + this.memberCount = 1L; + } else { + this.memberCount++; + } } public void decrementMemberCount() { diff --git a/src/main/java/com/example/onlyone/domain/club/service/ClubService.java b/src/main/java/com/example/onlyone/domain/club/service/ClubService.java index d96efbdd..705eda9f 100644 --- a/src/main/java/com/example/onlyone/domain/club/service/ClubService.java +++ b/src/main/java/com/example/onlyone/domain/club/service/ClubService.java @@ -50,7 +50,9 @@ public ClubCreateResponseDto createClub(ClubRequestDto requestDto) { Interest interest = interestRepository.findByCategory(Category.from(requestDto.getCategory())) .orElseThrow(() -> new CustomException(ErrorCode.INTEREST_NOT_FOUND)); Club club = requestDto.toEntity(interest); + club.incrementMemberCount(); clubRepository.save(club); + // 모임장의 UserClub 생성 User user = userService.getCurrentUser(); UserClub userClub = UserClub.builder() @@ -59,7 +61,7 @@ public ClubCreateResponseDto createClub(ClubRequestDto requestDto) { .clubRole(ClubRole.LEADER) .build(); userClubRepository.save(userClub); - club.incrementMemberCount(); + // 모임 전체 채팅방 생성 ChatRoom chatRoom = ChatRoom.builder() .club(club) diff --git a/src/main/java/com/example/onlyone/domain/feed/controller/FeedController.java b/src/main/java/com/example/onlyone/domain/feed/controller/FeedController.java index c2eaa2b1..94f67f2a 100644 --- a/src/main/java/com/example/onlyone/domain/feed/controller/FeedController.java +++ b/src/main/java/com/example/onlyone/domain/feed/controller/FeedController.java @@ -23,7 +23,7 @@ @RestController @Tag(name = "feed") @RequiredArgsConstructor -@RequestMapping("/clubs/{clubId}/feeds") +@RequestMapping("/api/clubs/{clubId}/feeds") public class FeedController { private final FeedService feedService; diff --git a/src/main/java/com/example/onlyone/domain/feed/controller/FeedMainController.java b/src/main/java/com/example/onlyone/domain/feed/controller/FeedMainController.java index 0fa542a2..1e720d75 100644 --- a/src/main/java/com/example/onlyone/domain/feed/controller/FeedMainController.java +++ b/src/main/java/com/example/onlyone/domain/feed/controller/FeedMainController.java @@ -25,7 +25,7 @@ @Tag(name = "feed-main", description = "전체 피드 조회 API") @RestController @RequiredArgsConstructor -@RequestMapping("/feeds") +@RequestMapping("/api/feeds") public class FeedMainController { private final FeedMainService feedMainService; diff --git a/src/main/java/com/example/onlyone/domain/image/controller/ImageController.java b/src/main/java/com/example/onlyone/domain/image/controller/ImageController.java index e9fbc4e2..2e0d3b1b 100644 --- a/src/main/java/com/example/onlyone/domain/image/controller/ImageController.java +++ b/src/main/java/com/example/onlyone/domain/image/controller/ImageController.java @@ -20,7 +20,7 @@ public class ImageController { private final ImageService imageService; @Operation(summary = "Presigned URL 생성", description = "S3에 이미지 업로드를 위한 Presigned URL을 생성합니다.") - @PostMapping("/{imageFolderType}/presigned-url") + @PostMapping("/api/{imageFolderType}/presigned-url") public ResponseEntity> generatePresignedUrl( @PathVariable String imageFolderType, @Valid @RequestBody PresignedUrlRequestDto request) { diff --git a/src/main/java/com/example/onlyone/domain/notification/controller/NotificationController.java b/src/main/java/com/example/onlyone/domain/notification/controller/NotificationController.java index 9174af5f..fd9406e9 100644 --- a/src/main/java/com/example/onlyone/domain/notification/controller/NotificationController.java +++ b/src/main/java/com/example/onlyone/domain/notification/controller/NotificationController.java @@ -17,7 +17,7 @@ @Tag(name = "알림", description = "알림 관리 API") @RestController -@RequestMapping("/notifications") +@RequestMapping("/api/notifications") @RequiredArgsConstructor @Slf4j public class NotificationController { diff --git a/src/main/java/com/example/onlyone/domain/payment/controller/PaymentController.java b/src/main/java/com/example/onlyone/domain/payment/controller/PaymentController.java index 57e113b3..56e19da2 100644 --- a/src/main/java/com/example/onlyone/domain/payment/controller/PaymentController.java +++ b/src/main/java/com/example/onlyone/domain/payment/controller/PaymentController.java @@ -19,7 +19,7 @@ @RestController @Tag(name = "Payment") @RequiredArgsConstructor -@RequestMapping("/payments") +@RequestMapping("/api/payments") public class PaymentController { private final PaymentService paymentService; diff --git a/src/main/java/com/example/onlyone/domain/schedule/controller/ScheduleController.java b/src/main/java/com/example/onlyone/domain/schedule/controller/ScheduleController.java index 5903f4aa..0f929674 100644 --- a/src/main/java/com/example/onlyone/domain/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/onlyone/domain/schedule/controller/ScheduleController.java @@ -19,7 +19,7 @@ @RestController @Tag(name = "Schedule") @RequiredArgsConstructor -@RequestMapping("/clubs/{clubId}/schedules") +@RequestMapping("/api/clubs/{clubId}/schedules") public class ScheduleController { private final ScheduleService scheduleService; diff --git a/src/main/java/com/example/onlyone/domain/search/controller/SearchController.java b/src/main/java/com/example/onlyone/domain/search/controller/SearchController.java index ba045865..7d6227fb 100644 --- a/src/main/java/com/example/onlyone/domain/search/controller/SearchController.java +++ b/src/main/java/com/example/onlyone/domain/search/controller/SearchController.java @@ -13,7 +13,7 @@ @RestController @Tag(name = "Search") @RequiredArgsConstructor -@RequestMapping("/search") +@RequestMapping("/api/search") public class SearchController { private final SearchService searchService; diff --git a/src/main/java/com/example/onlyone/domain/settlement/controller/SettlementController.java b/src/main/java/com/example/onlyone/domain/settlement/controller/SettlementController.java index ff4f5746..a73cff0b 100644 --- a/src/main/java/com/example/onlyone/domain/settlement/controller/SettlementController.java +++ b/src/main/java/com/example/onlyone/domain/settlement/controller/SettlementController.java @@ -17,7 +17,7 @@ @RestController @Tag(name = "Settlement") @RequiredArgsConstructor -@RequestMapping("/clubs/{clubId}/schedules/{scheduleId}/settlements") +@RequestMapping("/api/clubs/{clubId}/schedules/{scheduleId}/settlements") public class SettlementController { private final SettlementService settlementService; diff --git a/src/main/java/com/example/onlyone/domain/user/controller/AuthController.java b/src/main/java/com/example/onlyone/domain/user/controller/AuthController.java index 01e5df4a..a62494c1 100644 --- a/src/main/java/com/example/onlyone/domain/user/controller/AuthController.java +++ b/src/main/java/com/example/onlyone/domain/user/controller/AuthController.java @@ -22,7 +22,7 @@ @Log4j2 @RestController -@RequestMapping("/auth") +@RequestMapping("/api/auth") @RequiredArgsConstructor public class AuthController { private final KakaoService kakaoService; @@ -31,22 +31,29 @@ public class AuthController { @PostMapping("/kakao/callback") public ResponseEntity kakaoLogin(@RequestParam String code) { + log.error("🔍 [DEBUG] kakaoLogin 시작 - code={}", code); + try { // 1. 인증 코드로 카카오 액세스 토큰 받기 String kakaoAccessToken = kakaoService.getAccessToken(code); + log.error("✅ [DEBUG] kakaoAccessToken={}", kakaoAccessToken); // 2. 카카오 액세스 토큰으로 사용자 정보 받기 Map kakaoUserInfo = kakaoService.getUserInfo(kakaoAccessToken); + log.error("✅ [DEBUG] kakaoUserInfo={}", kakaoUserInfo); // 3. 사용자 정보 저장 또는 업데이트 Map loginResult = userService.processKakaoLogin(kakaoUserInfo, kakaoAccessToken); User user = (User) loginResult.get("user"); boolean isNewUser = (boolean) loginResult.get("isNewUser"); + log.error("✅ [DEBUG] userId={}, isNewUser={}", user.getUserId(), isNewUser); // 4. JWT 토큰 생성 (Access + Refresh) Map tokens = userService.generateTokenPair(user); + log.error("✅ [DEBUG] tokens 생성됨 - accessToken={}, refreshToken={}", + tokens.get("accessToken"), tokens.get("refreshToken")); - // 5. refreshToken Redis에 저장 (VITE_API_BASE_URL local 시, 주석) + // 5. refreshToken Redis에 저장 (local dev 시 주석 처리 가능) // redisTemplate.opsForValue() // .set(user.getUserId().toString(), tokens.get("refreshToken"), Duration.ofMillis(REFRESH_TOKEN_EXPIRE_TIME)); @@ -57,12 +64,15 @@ public ResponseEntity kakaoLogin(@RequestParam String code) { isNewUser ); + log.error("✅ [DEBUG] 로그인 성공 - userId={}", user.getUserId()); return ResponseEntity.ok(CommonResponse.success(response)); + } catch (CustomException e) { - // CustomException은 그대로 재던지기 (탈퇴한 사용자 403 에러 포함) - throw e; + log.error("❌ [DEBUG] CustomException 발생 - code={}, message={}", + e.getErrorCode(), e.getMessage(), e); + throw e; // CustomException은 그대로 재던지기 } catch (Exception e) { - // 기타 예외는 502 에러로 처리 + log.error("❌ [DEBUG] Exception 발생 - message={}", e.getMessage(), e); throw new CustomException(ErrorCode.KAKAO_LOGIN_FAILED); } } diff --git a/src/main/java/com/example/onlyone/domain/user/controller/UserController.java b/src/main/java/com/example/onlyone/domain/user/controller/UserController.java index cb36a8e0..d30189df 100644 --- a/src/main/java/com/example/onlyone/domain/user/controller/UserController.java +++ b/src/main/java/com/example/onlyone/domain/user/controller/UserController.java @@ -25,7 +25,7 @@ */ @Tag(name = "사용자", description = "사용자 정보 및 설정 관리 API") @RestController -@RequestMapping("/users") +@RequestMapping("/api/users") @RequiredArgsConstructor @Slf4j public class UserController { diff --git a/src/main/java/com/example/onlyone/domain/user/repository/UserRepository.java b/src/main/java/com/example/onlyone/domain/user/repository/UserRepository.java index 75ee8ce6..d8c005f6 100644 --- a/src/main/java/com/example/onlyone/domain/user/repository/UserRepository.java +++ b/src/main/java/com/example/onlyone/domain/user/repository/UserRepository.java @@ -9,4 +9,5 @@ public interface UserRepository extends JpaRepository { Optional findByKakaoId(Long kakaoId); + Optional findByUserId(Long userId); } \ No newline at end of file diff --git a/src/main/java/com/example/onlyone/domain/wallet/controller/WalletController.java b/src/main/java/com/example/onlyone/domain/wallet/controller/WalletController.java index 5fe27038..34fc0b3f 100644 --- a/src/main/java/com/example/onlyone/domain/wallet/controller/WalletController.java +++ b/src/main/java/com/example/onlyone/domain/wallet/controller/WalletController.java @@ -17,7 +17,7 @@ @RestController @Tag(name = "Wallet") @RequiredArgsConstructor -@RequestMapping("/users/wallet") +@RequestMapping("/api/users/wallet") public class WalletController { private final WalletService walletService; diff --git a/src/main/java/com/example/onlyone/global/config/SecurityConfig.java b/src/main/java/com/example/onlyone/global/config/SecurityConfig.java index 7610e2b9..58ee85f1 100644 --- a/src/main/java/com/example/onlyone/global/config/SecurityConfig.java +++ b/src/main/java/com/example/onlyone/global/config/SecurityConfig.java @@ -52,17 +52,18 @@ public WebSecurityCustomizer webSecurityCustomizer() { } private static final String[] AUTH_WHITELIST = { - "/signup/**", - "/login/**", - "/token", - "/center", - "/email/**", + "/api/auth/kakao/callback/**", + "/api/signup/**", + "/api/login/**", + "/api/token", + "/api/center", + "/api/email/**", "/ws/**", // WebSocket STOMP 엔드포인트 허용 "/ws/chat/**", // SockJS는 /info, /websocket, /xhr 등 내부 경로 씀 // "/sse/subscribe/**", // SSE는 별도 필터에서 인증 처리 "/ws-native", - "/kakao/**", - "/auth/**", + "/api/kakao/**", + "/api/auth/**", "/grafana/**", // Grafana 대시보드 "/influxdb/**", // InfluxDB API "/write", // InfluxDB write @@ -73,6 +74,7 @@ public WebSecurityCustomizer webSecurityCustomizer() { public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOriginPatterns(List.of( + "https://buddkit.com", "http://localhost:8080", "http://localhost:5173", "https://only-one-front-delta.vercel.app", diff --git a/src/main/java/com/example/onlyone/global/filter/JwtAuthenticationFilter.java b/src/main/java/com/example/onlyone/global/filter/JwtAuthenticationFilter.java index 0ad1f554..d2a4fab9 100644 --- a/src/main/java/com/example/onlyone/global/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/onlyone/global/filter/JwtAuthenticationFilter.java @@ -61,6 +61,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse // 사용자 상태 확인 - 탈퇴한 사용자인 경우 인증 거부, GUEST와 ACTIVE는 허용 Optional userOpt = userRepository.findByKakaoId(kakaoId); + log.error("userOpt = {}", userOpt.orElse(null)); if (userOpt.isPresent()) { User user = userOpt.get(); diff --git a/src/main/resources/elasticsearch/club-create-settings.json b/src/main/resources/elasticsearch/club-create-settings.json index 25ebd63d..69466ad8 100644 --- a/src/main/resources/elasticsearch/club-create-settings.json +++ b/src/main/resources/elasticsearch/club-create-settings.json @@ -97,7 +97,7 @@ }, "createdAt": { "type": "date", - "format": "yyyy-MM-dd'T'HH:mm:ss.SSSSSS||yyyy-MM-dd'T'HH:mm:ss.SSS||yyyy-MM-dd'T'HH:mm:ss" + "format": "yyyy-MM-dd'T'HH:mm:ss" }, "searchText": { "type": "text", diff --git a/src/main/resources/elasticsearch/club-mapping.json b/src/main/resources/elasticsearch/club-mapping.json index f71b1629..57bf72d2 100644 --- a/src/main/resources/elasticsearch/club-mapping.json +++ b/src/main/resources/elasticsearch/club-mapping.json @@ -42,7 +42,7 @@ }, "createdAt": { "type": "date", - "format": "yyyy-MM-dd'T'HH:mm:ss.SSSSSS||yyyy-MM-dd'T'HH:mm:ss.SSS||yyyy-MM-dd'T'HH:mm:ss" + "format": "yyyy-MM-dd'T'HH:mm:ss" }, "searchText": { "type": "text",