Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
fb0437f
[feat] diractionDocumnet 초기 설계
zyovn Aug 3, 2025
633cf1c
Merge branch 'develop' of https://github.com/Wayble-Project/wayble-sp…
zyovn Aug 3, 2025
dbac114
[feat] place 엔티티 설계
zyovn Aug 3, 2025
8fadc51
[fix] place name nullable = true로 수정
zyovn Aug 3, 2025
a798c93
[feat] 장소 검색 후, 저장 API 구현
zyovn Aug 3, 2025
9a04447
[feat] direction mappings, settings
zyovn Aug 4, 2025
de642ba
[feat] directionEsRepository
zyovn Aug 4, 2025
75a7c98
[fix] mappings, settings 수정
zyovn Aug 4, 2025
22e3ed7
[refactor] tmap관련 패키지 common으로 이동
zyovn Aug 4, 2025
d0b6f8d
[feat] dto 구현
zyovn Aug 4, 2025
def26d2
[fix] 길찾기 api 사용시 토큰이 필요하도록 수정
hyoinYang Aug 5, 2025
7c53b70
[fix] POST로 변경
hyoinYang Aug 5, 2025
482f59a
[refactor] edge의 빌더 accessLevel 축소
hyoinYang Aug 5, 2025
9e159a9
[feat] 길찾기 검색 자동완성 API
zyovn Aug 5, 2025
6b4fa94
[remove] 불필요한 클래스 및 메서드 삭제
zyovn Aug 5, 2025
2c32ec5
[remove] 중복되는 변수 삭제
zyovn Aug 5, 2025
922edb0
[docs] directionSwagger 작성
zyovn Aug 6, 2025
6a80a37
[docs] walkingSwagger 수정
zyovn Aug 6, 2025
7811fdf
[feat] Discord Webhook 에러 알림 기능 구현
KiSeungMin Aug 6, 2025
73cee64
[chore] log.debug -> info로 수정
KiSeungMin Aug 6, 2025
8f013ae
[fix] GET에서 POST로 변경
hyoinYang Aug 6, 2025
8b13e96
[refactor] Nullable 이용
hyoinYang Aug 6, 2025
0778e07
[refactor] 디버깅 메시지를 log로 변경
hyoinYang Aug 6, 2025
29ab83d
[feat] Discord Webhook 에러 알림 기능 구현
KiSeungMin Aug 6, 2025
632d878
[refactor] 오타, 빠진 필드 수정, 디버깅 메시지 삭제
hyoinYang Aug 6, 2025
fb1e4c8
[fix] 관리자 페이지 유저 조회 시 userType이 null이면 조회가 되지 않던 버그 수정
KiSeungMin Aug 6, 2025
5f599ef
[feat] 유저 회원가입, 토큰 갱신 시 로그 저장 기능 구현 완료
KiSeungMin Aug 6, 2025
2d0e790
[feat] 카카오 최초 로그인 시에도 로그가 남도록 구현
KiSeungMin Aug 6, 2025
5e1c71c
[feat] 카카오 최초 로그인 시에도 로그가 남도록 구현
KiSeungMin Aug 6, 2025
6555ebf
[feat] 관리자 페이지에서 일일 가입자 수, 방문자 수 조회 기능 구현
KiSeungMin Aug 6, 2025
c51a3a8
[fix] 관리자 페이지에서 유저 null 필드 관련 오류 해결
KiSeungMin Aug 6, 2025
20b6563
[fix] JPA userId 매핑을 위한 명시적 칼럼 name 추가
seung-in-Yoo Aug 6, 2025
a9a3ef9
[test] workflow 임시 수정
seung-in-Yoo Aug 6, 2025
7b8d6fa
[fix] JPA Lazy Loading, No Session 문제 해결
seung-in-Yoo Aug 6, 2025
618212a
[test] workflow 원상 복구
seung-in-Yoo Aug 6, 2025
19801dd
[fix] 배포 서버에서 로그인,유저 정보 조회 관련 에러 해결
KiSeungMin Aug 6, 2025
ae634f9
Merge branch 'develop' into feature/seungmin
KiSeungMin Aug 6, 2025
475bdfc
[test] test를 위해 workflow 임시 수정
KiSeungMin Aug 6, 2025
04af9de
[chore] workflow 원복
KiSeungMin Aug 6, 2025
c7f79ff
[feat] FileUpload 시 날짜별로 구분해서 저장할 수 있도록 기능 개선, 삭제 메서드 추가
KiSeungMin Aug 6, 2025
0813926
[feat] 파일 업로드 관련 예외 객체 정의
KiSeungMin Aug 6, 2025
d9be59d
[feat] 파일 업로드 컨트롤러 구현
KiSeungMin Aug 6, 2025
7ee87ef
[feat] 관리자 페이지에서 웨이블존 이미지 등록/수정 기능 구현
KiSeungMin Aug 6, 2025
30b4c7a
[refactor] webconfig 이용
hyoinYang Aug 6, 2025
35135ef
[refactor] 직선거리 30km 이상일 경우 바로 반환
hyoinYang Aug 6, 2025
c2b8c9f
Merge remote-tracking branch 'origin/develop' into feature/hyoin
hyoinYang Aug 6, 2025
4517480
[fix] list를 내부에서 받도록 수정
zyovn Aug 6, 2025
3d69dc3
[docs] response schema 작성
zyovn Aug 6, 2025
4f1a31e
[fix] api명 수정
zyovn Aug 6, 2025
3581c05
[refactor] 상수 수정
hyoinYang Aug 6, 2025
321737d
[refactor] log 수정
hyoinYang Aug 6, 2025
204c949
[fix] 관리자 페이지에서 웨이블존 등록 시 이미지가 저장되지 않던 버그 수정
KiSeungMin Aug 6, 2025
59d2c31
[feat] 관리자 페이지에 당일 가입자 수, 방문자 수 조회 로직 추가
KiSeungMin Aug 6, 2025
bef0fef
Merge branch 'develop' into feature/hyoin
KiSeungMin Aug 6, 2025
882ac71
[feat] 관리자 페이지에서 웨이블존 대표 이미지를 볼 수 있도록 구현
KiSeungMin Aug 6, 2025
1c416d0
[fix] 이미지 삭제 엔드포인트 제거
KiSeungMin Aug 6, 2025
c2fbee4
[feat] 웨이블존 제거 시, 이미지도 함께 제거되도록 구현
KiSeungMin Aug 6, 2025
664f789
[fix] 피드백 반영
zyovn Aug 6, 2025
bb47cea
[fix] 충돌 해결
zyovn Aug 6, 2025
54f1ee6
Merge pull request #109 from Wayble-Project/feature/jeongbin
zyovn Aug 6, 2025
79f0b0f
Merge remote-tracking branch 'origin/develop' into feature/hyoin
hyoinYang Aug 7, 2025
71aa22c
Merge branch 'feature/hyoin' of https://github.com/Wayble-Project/way…
hyoinYang Aug 7, 2025
7a67a62
Merge pull request #110 from Wayble-Project/feature/hyoin
hyoinYang Aug 7, 2025
302580e
Merge branch 'develop' into feature/seungmin
KiSeungMin Aug 7, 2025
31720d8
[fix] merge conflict 해결
KiSeungMin Aug 7, 2025
9fcb718
[test] CI/CD 배포 테스트
KiSeungMin Aug 7, 2025
bf4756d
[test] CI/CD 배포 테스트 완료
KiSeungMin Aug 7, 2025
9f7c1e4
[feat] 파일 업로드 기능 개선 및 API 구현, 관리자 페이지 웨이블존 이미지 등록&수정 기능 구현
KiSeungMin Aug 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/cd-develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name: CI/CD
on:
# push:
# branches: [ "feature/seungmin" ]

#push:
# branches: [ "feature/seungin" ]
pull_request:
branches: [ "main" ]

Expand Down Expand Up @@ -186,7 +189,7 @@ jobs:
run: |
curl -H "Content-Type: application/json" \
-X POST \
-d "{\"content\": \"✅ EC2 배포 성공! (브랜치: develop)\"}" \
-d "{\"content\": \"✅ EC2 배포 성공!\"}" \
${{ secrets.DISCORD_WEBHOOK_URL }}

# ❌ 배포 실패 알림 (Discord)
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/com/wayble/server/ServerApplication.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.wayble.server;

import com.wayble.server.direction.external.tmap.TMapProperties;
import com.wayble.server.common.client.tmap.TMapProperties;
import com.wayble.server.direction.external.kric.KricProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration;
Expand All @@ -15,8 +16,8 @@
)
@EnableJpaAuditing
@EnableScheduling
@EnableElasticsearchRepositories(basePackages = "com.wayble.server.explore.repository")
@EnableConfigurationProperties(TMapProperties.class)
@EnableConfigurationProperties({TMapProperties.class, KricProperties.class})
@EnableElasticsearchRepositories(basePackages = {"com.wayble.server.explore.repository", "com.wayble.server.logging.repository", "com.wayble.server.direction.repository"})
@EntityScan(basePackages = "com.wayble.server")
public class ServerApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.wayble.server.admin.controller;

import com.wayble.server.admin.dto.DailyStatsDto;
import com.wayble.server.admin.dto.SystemStatusDto;
import com.wayble.server.admin.service.AdminSystemService;
import com.wayble.server.admin.service.AdminUserService;
import com.wayble.server.admin.service.AdminWaybleZoneService;
import com.wayble.server.common.response.CommonResponse;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -14,6 +16,7 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Slf4j
@Controller
Expand Down Expand Up @@ -74,13 +77,17 @@ public String adminDashboard(HttpSession session, Model model) {
adminSystemService.isFileStorageHealthy()
);

// 통계 데이터 조회
// 일일 통계 데이터 조회
DailyStatsDto dailyStats = adminSystemService.getDailyStats();

// 기존 통계 데이터 조회 (원래대로 복구)
long totalUserCount = adminUserService.getTotalUserCount();
long totalDeletedUserCount = adminUserService.getTotalDeletedUserCount();
long totalWaybleZoneCount = adminWaybleZoneService.getTotalWaybleZoneCounts();

model.addAttribute("adminUsername", session.getAttribute("adminUsername"));
model.addAttribute("systemStatus", systemStatus);
model.addAttribute("dailyStats", dailyStats);
model.addAttribute("totalUserCount", totalUserCount);
model.addAttribute("totalDeletedUserCount", totalDeletedUserCount);
model.addAttribute("totalWaybleZoneCount", totalWaybleZoneCount);
Expand All @@ -93,4 +100,16 @@ public String adminLogout(HttpSession session) {
log.info("관리자 로그아웃");
return "redirect:/admin";
}

@GetMapping("/api/daily-stats")
@ResponseBody
public CommonResponse<DailyStatsDto> getDailyStats(HttpSession session) {
// 로그인 확인 (API용)
if (session.getAttribute("adminLoggedIn") == null) {
return CommonResponse.error(401, "관리자 인증이 필요합니다.");
}

DailyStatsDto dailyStats = adminSystemService.getDailyStats();
return CommonResponse.success(dailyStats);
}
Comment on lines +104 to +114
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

API 엔드포인트 개선사항 제안

새로운 일일 통계 API가 잘 구현되었으나 몇 가지 개선할 점이 있습니다:

  1. HTTP 상태 코드 하드코딩
  2. 중복된 인증 확인 로직
  3. 예외 처리 부족

다음과 같이 개선하세요:

+private static final int UNAUTHORIZED_STATUS = 401;
+private static final String UNAUTHORIZED_MESSAGE = "관리자 인증이 필요합니다.";
+
+private boolean isAdminAuthenticated(HttpSession session) {
+    return session.getAttribute("adminLoggedIn") != null;
+}

 @GetMapping("/api/daily-stats")
 @ResponseBody
 public CommonResponse<DailyStatsDto> getDailyStats(HttpSession session) {
-    // 로그인 확인 (API용)
-    if (session.getAttribute("adminLoggedIn") == null) {
-        return CommonResponse.error(401, "관리자 인증이 필요합니다.");
+    if (!isAdminAuthenticated(session)) {
+        return CommonResponse.error(UNAUTHORIZED_STATUS, UNAUTHORIZED_MESSAGE);
     }
     
-    DailyStatsDto dailyStats = adminSystemService.getDailyStats();
-    return CommonResponse.success(dailyStats);
+    try {
+        DailyStatsDto dailyStats = adminSystemService.getDailyStats();
+        return CommonResponse.success(dailyStats);
+    } catch (Exception e) {
+        log.error("일일 통계 조회 중 오류 발생", e);
+        return CommonResponse.error(500, "통계 데이터 조회에 실패했습니다.");
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@GetMapping("/api/daily-stats")
@ResponseBody
public CommonResponse<DailyStatsDto> getDailyStats(HttpSession session) {
// 로그인 확인 (API용)
if (session.getAttribute("adminLoggedIn") == null) {
return CommonResponse.error(401, "관리자 인증이 필요합니다.");
}
DailyStatsDto dailyStats = adminSystemService.getDailyStats();
return CommonResponse.success(dailyStats);
}
private static final int UNAUTHORIZED_STATUS = 401;
private static final String UNAUTHORIZED_MESSAGE = "관리자 인증이 필요합니다.";
private boolean isAdminAuthenticated(HttpSession session) {
return session.getAttribute("adminLoggedIn") != null;
}
@GetMapping("/api/daily-stats")
@ResponseBody
public CommonResponse<DailyStatsDto> getDailyStats(HttpSession session) {
if (!isAdminAuthenticated(session)) {
return CommonResponse.error(UNAUTHORIZED_STATUS, UNAUTHORIZED_MESSAGE);
}
try {
DailyStatsDto dailyStats = adminSystemService.getDailyStats();
return CommonResponse.success(dailyStats);
} catch (Exception e) {
log.error("일일 통계 조회 중 오류 발생", e);
return CommonResponse.error(500, "통계 데이터 조회에 실패했습니다.");
}
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/admin/controller/AdminController.java lines
104 to 114, the getDailyStats API endpoint has hardcoded HTTP status codes,
duplicated authentication checks, and lacks exception handling. Refactor to use
standardized HTTP status constants instead of hardcoded values, extract the
authentication check into a reusable method or interceptor to avoid duplication,
and add try-catch blocks around service calls to handle exceptions gracefully,
returning appropriate error responses.

}
15 changes: 15 additions & 0 deletions src/main/java/com/wayble/server/admin/dto/DailyStatsDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.wayble.server.admin.dto;

import lombok.Builder;

import java.time.LocalDate;

@Builder
public record DailyStatsDto(
LocalDate date,
long dailyRegistrationCount,
long dailyActiveUserCount,
long totalUserCount,
long totalWaybleZoneCount
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.cluster.HealthResponse;
import com.wayble.server.admin.dto.DailyStatsDto;
import com.wayble.server.logging.service.UserActionLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.sql.Connection;
import java.time.LocalDate;

@Slf4j
@Service
Expand All @@ -16,6 +19,9 @@ public class AdminSystemService {

private final ElasticsearchClient elasticsearchClient;
private final DataSource dataSource;
private final UserActionLogService userActionLogService;
private final AdminUserService adminUserService;
private final AdminWaybleZoneService adminWaybleZoneService;

public boolean isElasticsearchHealthy() {
try {
Expand Down Expand Up @@ -46,4 +52,42 @@ public boolean isFileStorageHealthy() {
// 파일 스토리지 상태 체크 (예: S3 연결 확인 등)
return true;
}

public DailyStatsDto getDailyStats() {
try {
LocalDate today = LocalDate.now();

// 일일 가입자 수
long dailyRegistrationCount = userActionLogService.getTodayUserRegistrationCount();

// 일일 활성 유저 수 (방문자)
long dailyActiveUserCount = userActionLogService.getTodayActiveUserCount();

// 전체 유저 수
long totalUserCount = adminUserService.getTotalUserCount();

// 전체 웨이블존 수
long totalWaybleZoneCount = adminWaybleZoneService.getTotalWaybleZoneCounts();

return DailyStatsDto.builder()
.date(today)
.dailyRegistrationCount(dailyRegistrationCount)
.dailyActiveUserCount(dailyActiveUserCount)
.totalUserCount(totalUserCount)
.totalWaybleZoneCount(totalWaybleZoneCount)
.build();

} catch (Exception e) {
log.error("Failed to get daily stats", e);

// 에러 발생시 기본값 반환
return DailyStatsDto.builder()
.date(LocalDate.now())
.dailyRegistrationCount(0)
.dailyActiveUserCount(0)
.totalUserCount(0)
.totalWaybleZoneCount(0)
.build();
}
}
Comment on lines +56 to +92
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

일일 통계 메서드 개선사항 제안

기본적인 구현은 잘되어 있으나, 개별 통계 수집 실패 시에도 다른 통계는 정상 수집할 수 있도록 개선하면 좋겠습니다.

다음과 같이 개별 예외 처리를 추가하여 부분적인 실패에도 대응하도록 개선하세요:

 public DailyStatsDto getDailyStats() {
-    try {
         LocalDate today = LocalDate.now();
         
-        // 일일 가입자 수
-        long dailyRegistrationCount = userActionLogService.getTodayUserRegistrationCount();
+        // 개별 통계 수집 (실패해도 다른 통계는 계속 수집)
+        long dailyRegistrationCount = 0;
+        long dailyActiveUserCount = 0;
+        long totalUserCount = 0;
+        long totalWaybleZoneCount = 0;
         
-        // 일일 활성 유저 수 (방문자)
-        long dailyActiveUserCount = userActionLogService.getTodayActiveUserCount();
+        try {
+            dailyRegistrationCount = userActionLogService.getTodayUserRegistrationCount();
+        } catch (Exception e) {
+            log.warn("일일 가입자 수 조회 실패", e);
+        }
         
-        // 전체 유저 수
-        long totalUserCount = adminUserService.getTotalUserCount();
+        try {
+            dailyActiveUserCount = userActionLogService.getTodayActiveUserCount();
+        } catch (Exception e) {
+            log.warn("일일 활성 유저 수 조회 실패", e);
+        }
         
-        // 전체 웨이블존 수
-        long totalWaybleZoneCount = adminWaybleZoneService.getTotalWaybleZoneCounts();
+        try {
+            totalUserCount = adminUserService.getTotalUserCount();
+        } catch (Exception e) {
+            log.warn("전체 유저 수 조회 실패", e);
+        }
+        
+        try {
+            totalWaybleZoneCount = adminWaybleZoneService.getTotalWaybleZoneCounts();
+        } catch (Exception e) {
+            log.warn("전체 웨이블존 수 조회 실패", e);
+        }
         
         return DailyStatsDto.builder()
                 .date(today)
                 .dailyRegistrationCount(dailyRegistrationCount)
                 .dailyActiveUserCount(dailyActiveUserCount)
                 .totalUserCount(totalUserCount)
                 .totalWaybleZoneCount(totalWaybleZoneCount)
                 .build();
-                
-    } catch (Exception e) {
-        log.error("Failed to get daily stats", e);
-        
-        // 에러 발생시 기본값 반환
-        return DailyStatsDto.builder()
-                .date(LocalDate.now())
-                .dailyRegistrationCount(0)
-                .dailyActiveUserCount(0)
-                .totalUserCount(0)
-                .totalWaybleZoneCount(0)
-                .build();
-    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public DailyStatsDto getDailyStats() {
try {
LocalDate today = LocalDate.now();
// 일일 가입자 수
long dailyRegistrationCount = userActionLogService.getTodayUserRegistrationCount();
// 일일 활성 유저 수 (방문자)
long dailyActiveUserCount = userActionLogService.getTodayActiveUserCount();
// 전체 유저 수
long totalUserCount = adminUserService.getTotalUserCount();
// 전체 웨이블존 수
long totalWaybleZoneCount = adminWaybleZoneService.getTotalWaybleZoneCounts();
return DailyStatsDto.builder()
.date(today)
.dailyRegistrationCount(dailyRegistrationCount)
.dailyActiveUserCount(dailyActiveUserCount)
.totalUserCount(totalUserCount)
.totalWaybleZoneCount(totalWaybleZoneCount)
.build();
} catch (Exception e) {
log.error("Failed to get daily stats", e);
// 에러 발생시 기본값 반환
return DailyStatsDto.builder()
.date(LocalDate.now())
.dailyRegistrationCount(0)
.dailyActiveUserCount(0)
.totalUserCount(0)
.totalWaybleZoneCount(0)
.build();
}
}
public DailyStatsDto getDailyStats() {
LocalDate today = LocalDate.now();
// 개별 통계 수집 (실패해도 다른 통계는 계속 수집)
long dailyRegistrationCount = 0;
long dailyActiveUserCount = 0;
long totalUserCount = 0;
long totalWaybleZoneCount = 0;
try {
dailyRegistrationCount = userActionLogService.getTodayUserRegistrationCount();
} catch (Exception e) {
log.warn("일일 가입자 수 조회 실패", e);
}
try {
dailyActiveUserCount = userActionLogService.getTodayActiveUserCount();
} catch (Exception e) {
log.warn("일일 활성 유저 수 조회 실패", e);
}
try {
totalUserCount = adminUserService.getTotalUserCount();
} catch (Exception e) {
log.warn("전체 유저 수 조회 실패", e);
}
try {
totalWaybleZoneCount = adminWaybleZoneService.getTotalWaybleZoneCounts();
} catch (Exception e) {
log.warn("전체 웨이블존 수 조회 실패", e);
}
return DailyStatsDto.builder()
.date(today)
.dailyRegistrationCount(dailyRegistrationCount)
.dailyActiveUserCount(dailyActiveUserCount)
.totalUserCount(totalUserCount)
.totalWaybleZoneCount(totalWaybleZoneCount)
.build();
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/admin/service/AdminSystemService.java between
lines 56 and 92, the getDailyStats method currently catches all exceptions in a
single try-catch block, causing all stats to fail if one fails. Refactor by
wrapping each individual stats retrieval call in its own try-catch block to
handle exceptions separately, allowing successful retrieval of other stats even
if one fails. Initialize each stat variable with a default value and update it
only if the corresponding service call succeeds.

}
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ private AdminUserThumbnailDto convertToThumbnailDto(Object[] row) {
String email = (String) row[2];
LocalDate birthDate = row[3] != null ? ((Date) row[3]).toLocalDate() : null;
Gender gender = row[4] != null ? Gender.valueOf((String) row[4]) : null;
LoginType loginType = LoginType.valueOf((String) row[5]);
UserType userType = UserType.valueOf((String) row[6]);
LoginType loginType = row[5] != null ? LoginType.valueOf((String) row[5]) : null;
UserType userType = row[6] != null ? UserType.valueOf((String) row[6]) : null;
String disabilityType = (String) row[7];
String mobilityAid = (String) row[8];

Expand All @@ -181,8 +181,8 @@ private AdminUserDetailDto convertToDetailDto(Object[] row, long reviewCount, lo
String email = (String) row[3];
LocalDate birthDate = row[4] != null ? ((Date) row[4]).toLocalDate() : null;
Gender gender = row[5] != null ? Gender.valueOf((String) row[5]) : null;
LoginType loginType = LoginType.valueOf((String) row[6]);
UserType userType = UserType.valueOf((String) row[7]);
LoginType loginType = row[6] != null ? LoginType.valueOf((String) row[6]) : null;
UserType userType = row[7] != null ? UserType.valueOf((String) row[7]) : null;
String profileImageUrl = (String) row[8];
String disabilityType = (String) row[9];
String mobilityAid = (String) row[10];
Expand All @@ -203,8 +203,8 @@ private AdminUserDetailDto convertToDetailDtoWithStats(Object[] row, long review
String email = (String) row[3];
LocalDate birthDate = row[4] != null ? ((Date) row[4]).toLocalDate() : null;
Gender gender = row[5] != null ? Gender.valueOf((String) row[5]) : null;
LoginType loginType = LoginType.valueOf((String) row[6]);
UserType userType = UserType.valueOf((String) row[7]);
LoginType loginType = row[6] != null ? LoginType.valueOf((String) row[6]) : null;
UserType userType = row[7] != null ? UserType.valueOf((String) row[7]) : null;
String profileImageUrl = (String) row[8];
String disabilityType = (String) row[9];
String mobilityAid = (String) row[10];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.wayble.server.admin.dto.wayblezone.AdminWaybleZoneUpdateDto;
import com.wayble.server.admin.exception.AdminErrorCase;
import com.wayble.server.admin.repository.AdminWaybleZoneRepository;
import com.wayble.server.aws.AmazonS3Manager;
import com.wayble.server.common.exception.ApplicationException;
import com.wayble.server.explore.service.WaybleZoneDocumentService;
import com.wayble.server.user.repository.UserPlaceWaybleZoneMappingRepository;
Expand All @@ -31,6 +32,7 @@ public class AdminWaybleZoneService {
private final WaybleZoneRepository waybleZoneRepository;
private final UserPlaceWaybleZoneMappingRepository userPlaceWaybleZoneMappingRepository;
private final WaybleZoneVisitLogRepository waybleZoneVisitLogRepository;
private final AmazonS3Manager amazonS3Manager;

public long getTotalWaybleZoneCounts() {
return adminWaybleZoneRepository.count();
Expand Down Expand Up @@ -87,6 +89,18 @@ public Long updateWaybleZone(AdminWaybleZoneUpdateDto updateDto) {
WaybleZone waybleZone = waybleZoneRepository.findById(updateDto.id())
.orElseThrow(() -> new ApplicationException(AdminErrorCase.WAYBLE_ZONE_NOT_FOUND));

// 메인 이미지 변경 시 기존 이미지 S3에서 삭제
String oldImageUrl = waybleZone.getMainImageUrl();
String newImageUrl = updateDto.mainImageUrl();
if (oldImageUrl != null && !oldImageUrl.equals(newImageUrl)) {
try {
amazonS3Manager.deleteImageFileFromS3(oldImageUrl);
log.info("기존 메인 이미지 삭제 완료 - URL: {}", oldImageUrl);
} catch (Exception e) {
log.warn("기존 메인 이미지 삭제 실패 - URL: {}, 오류: {}", oldImageUrl, e.getMessage());
}
}
Comment on lines +92 to +102
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

S3 이미지 삭제 로직을 별도 메서드로 분리하세요.

updateWaybleZone과 deleteWaybleZone 메서드에서 S3 이미지 삭제 로직이 중복됩니다. 코드 중복을 제거하고 가독성을 향상시키기 위해 별도 메서드로 분리하는 것을 권장합니다.

+    private void deleteImageFromS3(String imageUrl, String context) {
+        if (imageUrl != null) {
+            try {
+                amazonS3Manager.deleteImageFileFromS3(imageUrl);
+                log.info("{} 이미지 삭제 완료 - URL: {}", context, imageUrl);
+            } catch (Exception e) {
+                log.warn("{} 이미지 삭제 실패 - URL: {}, 오류: {}", context, imageUrl, e.getMessage());
+            }
+        }
+    }

그런 다음 중복된 코드를 이 메서드 호출로 대체하세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 메인 이미지 변경 시 기존 이미지 S3에서 삭제
String oldImageUrl = waybleZone.getMainImageUrl();
String newImageUrl = updateDto.mainImageUrl();
if (oldImageUrl != null && !oldImageUrl.equals(newImageUrl)) {
try {
amazonS3Manager.deleteImageFileFromS3(oldImageUrl);
log.info("기존 메인 이미지 삭제 완료 - URL: {}", oldImageUrl);
} catch (Exception e) {
log.warn("기존 메인 이미지 삭제 실패 - URL: {}, 오류: {}", oldImageUrl, e.getMessage());
}
}
// Add this helper to AdminWaybleZoneService to centralize S3 image deletion
private void deleteImageFromS3(String imageUrl, String context) {
if (imageUrl != null) {
try {
amazonS3Manager.deleteImageFileFromS3(imageUrl);
log.info("{} 이미지 삭제 완료 - URL: {}", context, imageUrl);
} catch (Exception e) {
log.warn("{} 이미지 삭제 실패 - URL: {}, 오류: {}", context, imageUrl, e.getMessage());
}
}
}
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java
around lines 92 to 102, the S3 image deletion logic is duplicated in
updateWaybleZone and deleteWaybleZone methods. To fix this, extract the S3 image
deletion code into a separate private method that accepts the image URL as a
parameter. Then replace the duplicated deletion code in both methods with calls
to this new method to improve code reuse and readability.


// 주소 정보 업데이트
com.wayble.server.common.entity.Address updatedAddress = com.wayble.server.common.entity.Address
.builder()
Expand Down Expand Up @@ -135,6 +149,16 @@ public void deleteWaybleZone(Long waybleZoneId) {

log.info("웨이블존 삭제 시작 - ID: {}, 이름: {}", waybleZoneId, waybleZone.getZoneName());

// 메인 이미지 S3에서 삭제
if (waybleZone.getMainImageUrl() != null) {
try {
amazonS3Manager.deleteImageFileFromS3(waybleZone.getMainImageUrl());
log.info("메인 이미지 삭제 완료 - URL: {}", waybleZone.getMainImageUrl());
} catch (Exception e) {
log.warn("메인 이미지 삭제 실패 - URL: {}, 오류: {}", waybleZone.getMainImageUrl(), e.getMessage());
}
}
Comment on lines +152 to +160
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

중복된 S3 삭제 로직을 통합된 메서드로 교체하세요.

위에서 제안한 deleteImageFromS3 메서드를 사용하여 코드 중복을 제거하세요.

-            // 메인 이미지 S3에서 삭제
-            if (waybleZone.getMainImageUrl() != null) {
-                try {
-                    amazonS3Manager.deleteImageFileFromS3(waybleZone.getMainImageUrl());
-                    log.info("메인 이미지 삭제 완료 - URL: {}", waybleZone.getMainImageUrl());
-                } catch (Exception e) {
-                    log.warn("메인 이미지 삭제 실패 - URL: {}, 오류: {}", waybleZone.getMainImageUrl(), e.getMessage());
-                }
-            }
+            // 메인 이미지 S3에서 삭제
+            deleteImageFromS3(waybleZone.getMainImageUrl(), "메인");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 메인 이미지 S3에서 삭제
if (waybleZone.getMainImageUrl() != null) {
try {
amazonS3Manager.deleteImageFileFromS3(waybleZone.getMainImageUrl());
log.info("메인 이미지 삭제 완료 - URL: {}", waybleZone.getMainImageUrl());
} catch (Exception e) {
log.warn("메인 이미지 삭제 실패 - URL: {}, 오류: {}", waybleZone.getMainImageUrl(), e.getMessage());
}
}
// 메인 이미지 S3에서 삭제
deleteImageFromS3(waybleZone.getMainImageUrl(), "메인");
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java
around lines 152 to 160, replace the existing S3 main image deletion logic with
a call to the unified deleteImageFromS3 method to remove code duplication. This
involves removing the try-catch block and direct calls to
amazonS3Manager.deleteImageFileFromS3 and instead invoking deleteImageFromS3
with the main image URL, ensuring consistent error handling and logging.


// 연관된 리뷰들 soft delete
waybleZone.getReviewList().forEach(review -> {
review.softDelete();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class RefreshToken {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true) // 1인 1토큰
@Column(name = "user_id", nullable = false, unique = true)
private Long userId;

@Column(nullable = false)
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/wayble/server/auth/service/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.wayble.server.common.config.security.jwt.JwtTokenProvider;
import com.wayble.server.common.exception.ApplicationException;
import com.wayble.server.logging.service.UserActionLogService;
import com.wayble.server.user.dto.UserLoginRequestDto;
import com.wayble.server.auth.dto.TokenResponseDto;
import com.wayble.server.auth.entity.RefreshToken;
Expand All @@ -22,6 +23,7 @@ public class AuthService {
private final RefreshTokenRepository refreshTokenRepository;
private final PasswordEncoder encoder;
private final JwtTokenProvider jwtProvider;
private final UserActionLogService userActionLogService;

public TokenResponseDto login(UserLoginRequestDto req) {
User user = userRepository.findByEmailAndLoginType(req.email(), req.loginType())
Expand All @@ -42,6 +44,12 @@ public TokenResponseDto login(UserLoginRequestDto req) {
.build();
refreshTokenRepository.save(entity);

// 첫 로그인시에도 활성 유저 로그 저장 (비동기, 하루 1회만)
userActionLogService.logTokenRefresh(
user.getId(),
user.getUserType() != null ? user.getUserType().name() : null
);

return new TokenResponseDto(accessToken, refreshToken);
}

Expand Down Expand Up @@ -73,6 +81,13 @@ public TokenResponseDto reissue(String refreshToken) {
refreshTokenRepository.save(saved);

String newAccessToken = jwtProvider.generateToken(userId, user.getUserType() != null ? user.getUserType().name() : null);

// 토큰 갱신 로그 저장 (비동기, 하루 1회만)
userActionLogService.logTokenRefresh(
userId,
user.getUserType() != null ? user.getUserType().name() : null
);

return new TokenResponseDto(newAccessToken, newRefreshToken);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wayble.server.common.config.security.jwt.JwtTokenProvider;
import com.wayble.server.common.exception.ApplicationException;
import com.wayble.server.logging.service.UserActionLogService;
import com.wayble.server.user.dto.KakaoLoginRequestDto;
import com.wayble.server.user.dto.KakaoLoginResponseDto;
import com.wayble.server.user.dto.KakaoUserInfoDto;
Expand All @@ -26,6 +27,7 @@ public class KakaoLoginService {
private final JwtTokenProvider jwtProvider;
private final ObjectMapper objectMapper;
private final WebClient webClient;
private final UserActionLogService userActionLogService;

private static final String KAKAO_USERINFO_URL = "https://kapi.kakao.com/v2/user/me";

Expand Down Expand Up @@ -66,6 +68,21 @@ public KakaoLoginResponseDto kakaoLogin(KakaoLoginRequestDto request) {
);
String refreshToken = jwtProvider.generateRefreshToken(user.getId());

// 로그 저장 (비동기)
if (isNewUser) {
// 신규 가입 로그
userActionLogService.logUserRegister(
user.getId(),
LoginType.KAKAO.name(),
user.getUserType() != null ? user.getUserType().name() : null
);
}

// 모든 카카오 로그인시 활성 유저 로그 저장 (하루 1회만)
userActionLogService.logTokenRefresh(
user.getId(),
user.getUserType() != null ? user.getUserType().name() : null
);
Comment on lines +71 to +85
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

로그 기록 로직을 별도 메서드로 분리하여 가독성을 개선하세요.

메인 로그인 로직과 로그 기록 로직이 섞여 있어 코드 가독성이 떨어집니다. 로그 기록을 별도 메서드로 분리하는 것을 권장합니다.

         // JWT 토큰 발급
         String accessToken = jwtProvider.generateToken(
                 user.getId(),
                 user.getUserType() != null ? user.getUserType().name() : null
         );
         String refreshToken = jwtProvider.generateRefreshToken(user.getId());
 
-        // 로그 저장 (비동기)
-        if (isNewUser) {
-            // 신규 가입 로그
-            userActionLogService.logUserRegister(
-                    user.getId(), 
-                    LoginType.KAKAO.name(), 
-                    user.getUserType() != null ? user.getUserType().name() : null
-            );
-        }
-        
-        // 모든 카카오 로그인시 활성 유저 로그 저장 (하루 1회만)
-        userActionLogService.logTokenRefresh(
-                user.getId(), 
-                user.getUserType() != null ? user.getUserType().name() : null
-        );
+        // 사용자 행동 로그 기록
+        logUserActions(user, isNewUser);

+    private void logUserActions(User user, boolean isNewUser) {
+        try {
+            if (isNewUser) {
+                userActionLogService.logUserRegister(
+                        user.getId(), 
+                        LoginType.KAKAO.name(), 
+                        user.getUserType() != null ? user.getUserType().name() : null
+                );
+            }
+            
+            userActionLogService.logTokenRefresh(
+                    user.getId(), 
+                    user.getUserType() != null ? user.getUserType().name() : null
+            );
+        } catch (Exception e) {
+            log.warn("사용자 행동 로그 기록 실패 - 사용자 ID: {}", user.getId(), e);
+        }
+    }
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/auth/service/KakaoLoginService.java around
lines 71 to 85, the logging logic is mixed with the main login process, reducing
readability. Refactor by extracting the user action logging code into a separate
private method that handles both new user registration logs and daily token
refresh logs. Then call this new method from the main login flow to keep the
code cleaner and more maintainable.


return KakaoLoginResponseDto.builder()
.accessToken(accessToken)
Expand Down
Loading
Loading