Skip to content

[feat] 관리자 페이지 구현 완료#94

Merged
KiSeungMin merged 27 commits into
developfrom
feature/seungmin
Aug 3, 2025
Merged

[feat] 관리자 페이지 구현 완료#94
KiSeungMin merged 27 commits into
developfrom
feature/seungmin

Conversation

@KiSeungMin

@KiSeungMin KiSeungMin commented Aug 2, 2025

Copy link
Copy Markdown
Member

✔️ 연관 이슈

📝 작업 내용

  • 데이터 관리 및 추적을 위해 관리자 페이지를 구현했습니다.
    • thymeleaf와 tailwind를 사용해 구현했습니다.
  • 현재 관리자 페이지에서 가능한 기능은 다음과 같습니다.
    • 유저 정보 확인
    • 웨이블존 조회, 생성, 수정, 삭제
    • soft delete된 유저 복구
    • 스프링 서버, elastic search, db 작동 여부 확인
  • /admin 경로로 접속해 관리자 페이지에 접속할 수 있습니다.
    • 관리자 아이디, 비밀번호는 노션의 application.yml 파일 확인 부탁드립니다.

스크린샷 (선택)

2025-08-02 20-31-29 2025-08-02 20-31-20 2025-08-02 20-31-55

Summary by CodeRabbit

New Features

  • 관리자 웹 인터페이스(어드민 대시보드, 로그인, 사용자/웨이블존 관리) 및 REST API가 추가되었습니다.
  • 어드민 대시보드에서 시스템 상태, 전체 사용자/웨이블존/삭제 사용자 수 등 주요 통계와 관리 메뉴 제공.
  • 사용자 및 삭제 사용자 목록, 상세 조회, 복원 기능, 페이징 및 검색 기능 지원.
  • 웨이블존 목록, 상세, 생성, 수정, 삭제 기능 및 최근 리뷰, 운영 시간, 이미지 등 상세 정보 제공.
  • 소프트 삭제 및 복원, 연관 데이터(리뷰, 즐겨찾기 등) 동기 복원 처리.

Bug Fixes

  • 없음

Chores

  • 타임리프(Thymeleaf) 템플릿 및 Tailwind CSS 기반의 관리자 화면 다수 추가.
  • 불필요한 import, 공백 등 코드 스타일 정리.

Documentation

  • 관리자 기능 관련 템플릿 및 서비스 구조 문서화.

Refactor

  • 엔티티 및 서비스에 소프트 삭제, 복원, 어드민 전용 업데이트 메서드 추가.
  • 웨이블존/사용자 관련 DTO 및 레포지토리 구조 개선.

Style

  • Tailwind CSS 및 커스텀 컬러 적용으로 관리자 UI 일관성 강화.

@KiSeungMin KiSeungMin self-assigned this Aug 2, 2025
@coderabbitai

coderabbitai Bot commented Aug 2, 2025

Copy link
Copy Markdown

Walkthrough

이 변경사항은 Wayble 서비스의 관리자 페이지를 신설하고, 관리자 웹 인터페이스 및 관련 백엔드 기능을 대규모로 추가합니다. 관리자 대시보드, 사용자 관리, 웨이블존 관리, 시스템 상태 확인 등 다양한 기능을 위한 컨트롤러, 서비스, 저장소, DTO, 타임리프 템플릿이 신규로 도입되었습니다. 일부 엔티티와 리포지토리, 보조 서비스도 관리자 기능에 맞게 확장되었습니다.

Changes

Cohort / File(s) Change Summary
Admin Controller 및 서비스 도입
src/main/java/com/wayble/server/admin/controller/AdminController.java, src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java, src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java, src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java, src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java, src/main/java/com/wayble/server/admin/service/AdminSystemService.java, src/main/java/com/wayble/server/admin/service/AdminUserService.java, src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java
관리자 로그인, 대시보드, 사용자/웨이블존 관리, 시스템 상태 확인 등 관리자 기능 컨트롤러 및 서비스 신규 추가
Admin DTO 추가
src/main/java/com/wayble/server/admin/dto/SystemStatusDto.java, src/main/java/com/wayble/server/admin/dto/user/AdminUserDetailDto.java, src/main/java/com/wayble/server/admin/dto/user/AdminUserPageDto.java, src/main/java/com/wayble/server/admin/dto/user/AdminUserThumbnailDto.java, src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneCreateDto.java, src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java, src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZonePageDto.java, src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneThumbnailDto.java, src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneUpdateDto.java
관리자 기능에 필요한 사용자/웨이블존/시스템 상태 DTO 및 페이징 DTO 등 신규 도입
Admin Repository 추가 및 확장
src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java, src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepository.java, src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryCustom.java, src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java
관리자용 사용자/웨이블존 데이터 접근 및 커스텀 쿼리 리포지토리 신규 추가
Admin 예외 Enum 추가
src/main/java/com/wayble/server/admin/exception/AdminErrorCase.java
관리자 도메인 전용 에러 케이스 Enum 도입
템플릿(Thymeleaf) 추가
src/main/resources/templates/admin/dashboard.html, src/main/resources/templates/admin/login.html, src/main/resources/templates/admin/user/users.html, src/main/resources/templates/admin/user/user-detail.html, src/main/resources/templates/admin/user/deleted-users.html, src/main/resources/templates/admin/user/deleted-user-detail.html, src/main/resources/templates/admin/wayblezone/wayble-zones.html, src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html, src/main/resources/templates/admin/wayblezone/wayble-zone-create.html, src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html
관리자 대시보드, 로그인, 사용자/웨이블존 관리, 삭제 사용자 관리 등 관리자 UI용 타임리프 템플릿 신규 추가
WaybleZone 엔티티/리포지토리 확장
src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java, src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepository.java, src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneVisitLogRepository.java
웨이블존 엔티티에 관리자 전용 업데이트 메소드, 리포지토리에 count 및 삭제 관련 메소드 추가
공통/기타 보조 변경
src/main/java/com/wayble/server/common/entity/BaseEntity.java, src/main/java/com/wayble/server/explore/service/WaybleZoneDocumentService.java
엔티티 softDelete 메소드 추가, 웨이블존 문서 삭제 메소드 추가 등 관리자 기능 지원
환경설정/빌드/워크플로우
build.gradle, .github/workflows/cd-develop.yml, src/main/java/com/wayble/server/common/config/SecurityConfig.java, src/main/java/com/wayble/server/user/repository/UserRepository.java, src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java
타임리프 의존성 추가, 워크플로우 주석 변경, 사소한 코드 정리 등

Sequence Diagram(s)

sequenceDiagram
    participant Admin as 관리자(브라우저)
    participant Web as AdminController
    participant UserSvc as AdminUserService
    participant ZoneSvc as AdminWaybleZoneService
    participant SysSvc as AdminSystemService
    participant DB as DB/Repository

    Admin->>Web: GET /admin (로그인 페이지)
    Web-->>Admin: 로그인 폼 반환

    Admin->>Web: POST /admin/login (로그인 시도)
    Web->>Web: 입력값 검증
    Web->>Web: 관리자 계정 비교
    alt 성공
        Web->>Admin: 대시보드 리다이렉트
    else 실패
        Web-->>Admin: 에러 메시지와 로그인 폼
    end

    Admin->>Web: GET /admin/dashboard
    Web->>SysSvc: 시스템 상태 조회
    SysSvc->>DB: DB/Elasticsearch 상태 체크
    Web->>UserSvc: 전체 사용자 수 조회
    Web->>ZoneSvc: 전체 웨이블존 수 조회
    Web-->>Admin: 대시보드 HTML 반환

    Admin->>Web: GET /admin/users?page=0
    Web->>UserSvc: 사용자 페이징 목록 조회
    UserSvc->>DB: 사용자 목록 쿼리
    Web-->>Admin: 사용자 목록 HTML 반환

    Admin->>Web: GET /admin/wayble-zones?page=0
    Web->>ZoneSvc: 웨이블존 페이징 목록 조회
    ZoneSvc->>DB: 웨이블존 목록 쿼리
    Web-->>Admin: 웨이블존 목록 HTML 반환
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
관리자 페이지 frontEnd 제작(tailwind, thymeleaf) (#85)
관리자 페이지에 필요한 서비스 로직 구현 (#85)

Assessment against linked issues: Out-of-scope changes

해당 변경사항에서 명백히 요구사항과 무관한 기능적 코드 변경은 발견되지 않았습니다.

Possibly related PRs

  • [CI] CI/CD 파이프라인 구축 #14: .github/workflows/cd-develop.yml 파일에 대한 CI/CD 파이프라인 신규 도입 PR로, 본 PR과 워크플로우 파일 변경 부분이 경합될 수 있음.

Suggested labels

💡 feature

Suggested reviewers

  • seung-in-Yoo
  • hyoinYang
  • zyovn
  • wonjun-lee-fcwj245

Poem

🐇

새벽 풀밭 위를 달려
관리자 페이지를 열었네
사용자, 웨이블존, 모두 한눈에
꼼꼼한 서비스, 튼튼한 백엔드
타임리프 꽃길 따라
토끼도 춤추는 Wayble의 봄
🥕✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/seungmin

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 26

🧹 Nitpick comments (28)
build.gradle (1)

34-34: Thymeleaf 의존성 추가 👍

Spring MVC + Thymeleaf 기반의 관리자 화면 구현 목적에 부합하며, 별다른 충돌 요소가 없어 보입니다.
다만, 같은 파일에 AWS SDK 의존성이 두 번(라인 39–40, 56–57) 선언되어 있어 Gradle 캐싱을 낭비할 수 있습니다. 추후 정리 권장드립니다.

src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java (1)

13-14: 사용되지 않는 Optional import와 불필요한 공백 제거 필요

java.util.Optional 을 추가했으나 실제 코드에서 사용하지 않고 있으며, 129 라인의 공백 라인 역시 의미가 없습니다.
빌드-도구의 unused-import 검사에 걸려 CI 실패 요인이 될 수 있으니 삭제하시길 권장합니다.

-import java.util.Optional;
-
-

Also applies to: 129-130

src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepository.java (1)

11-12: count() 메서드 선언은 중복입니다

JpaRepository 가 이미 long count() 를 기본 제공하므로 동일 시그니처 메서드를 다시 선언할 필요가 없습니다.
불필요한 선언은 유지보수 시 혼란을 줄 수 있으니 삭제를 권장합니다.

-    long count();
src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryCustom.java (1)

9-16: 관리자용 커스텀 리포지토리 인터페이스 승인

페이징 조회와 상세 조회를 위한 메서드 시그니처가 명확하게 정의되었습니다. 관리자 기능에 특화된 DTO를 사용하는 설계가 적절합니다.

메서드 파라미터에 대한 유효성 검사를 문서화하면 좋겠습니다:

 public interface AdminWaybleZoneRepositoryCustom {
     
-    // 페이징 조회 메서드 (관리자용)
+    /**
+     * 웨이블존 페이징 조회 (관리자용)
+     * @param page 페이지 번호 (0부터 시작)
+     * @param size 페이지 크기 (양수)
+     * @return 웨이블존 썸네일 DTO 목록
+     */
     List<AdminWaybleZoneThumbnailDto> findWaybleZonesWithPaging(int page, int size);
     
-    // 상세 조회 메서드 (관리자용)
+    /**
+     * 웨이블존 상세 정보 조회 (관리자용)
+     * @param zoneId 웨이블존 ID
+     * @return 웨이블존 상세 DTO (존재하지 않을 경우 empty)
+     */
     Optional<AdminWaybleZoneDetailDto> findAdminWaybleZoneDetailById(Long zoneId);
src/main/resources/templates/admin/login.html (1)

70-72: 비밀번호 필드 보안 속성 강화

비밀번호 필드에 추가 보안 속성을 설정하여 보안을 강화할 수 있습니다.

                        <input id="password" name="password" type="password" required 
+                               autocomplete="current-password" spellcheck="false"
                               class="appearance-none rounded-md relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 focus:outline-none focus:ring-wayble-blue focus:border-wayble-blue focus:z-10 sm:text-sm"
                               placeholder="비밀번호를 입력하세요">
src/main/java/com/wayble/server/admin/dto/user/AdminUserThumbnailDto.java (1)

9-19: 검증 어노테이션 추가를 고려해보세요.

DTO에 데이터 검증을 위한 어노테이션이 없습니다. 관리자 기능에서 데이터 무결성을 보장하기 위해 적절한 검증 어노테이션 추가를 권장합니다.

+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+
 public record AdminUserThumbnailDto(
+    @NotNull
     Long id,
+    @NotBlank
     String nickname,
+    @Email
     String email,
+    @NotNull
     LocalDate birthDate,
     Gender gender,
     LoginType loginType,
     UserType userType,
     String disabilityType,
     String mobilityAid
 ) {
src/main/java/com/wayble/server/admin/exception/AdminErrorCase.java (1)

11-12: 에러 코드 문서화를 고려해보세요.

에러 코드 9001, 9002의 범위와 규칙에 대한 문서화를 추가하면 향후 에러 코드 관리에 도움이 될 것입니다.

 public enum AdminErrorCase implements ErrorCase {
 
+    // Admin module error codes: 9001-9999
     USER_NOT_FOUND(404, 9001, "사용자를 찾을 수 없습니다."),
     WAYBLE_ZONE_NOT_FOUND(404, 9002, "해당 웨이블존을 찾을 수 없습니다.");
src/main/java/com/wayble/server/admin/dto/SystemStatusDto.java (1)

10-12: 정적 팩토리 메서드가 유용합니다.

of 메서드를 통해 객체 생성이 더욱 명확해졌습니다. 추가로 모든 상태가 정상인지 확인하는 헬퍼 메서드도 고려해볼 수 있습니다.

     public static SystemStatusDto of(boolean apiServer, boolean database, boolean elasticsearch, boolean fileStorage) {
         return new SystemStatusDto(apiServer, database, elasticsearch, fileStorage);
     }
+    
+    public boolean isAllHealthy() {
+        return apiServerStatus && databaseStatus && elasticsearchStatus && fileStorageStatus;
+    }
src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneThumbnailDto.java (1)

14-14: 주석을 더 상세하게 작성해보세요.

facilityInfo 필드의 주석이 간단합니다. 시설 정보가 어떤 형태로 요약되는지 더 구체적으로 설명하면 도움이 될 것입니다.

-        WaybleZoneFacility facilityInfo // 시설 정보를 문자열로 요약
+        WaybleZoneFacility facilityInfo // 웨이블존의 시설 정보 (접근성, 편의시설 등)
src/main/resources/templates/admin/user/deleted-users.html (1)

104-112: 복잡한 조건부 클래스 할당 개선 필요

사용자 타입과 로그인 타입에 따른 스타일 클래스 할당 로직이 복잡하고 가독성이 떨어집니다.

Thymeleaf의 switch 문이나 별도 유틸리티 함수를 사용하여 개선할 수 있습니다:

                                    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
-                                          th:classappend="${user.userType.name() == 'GENERAL'} ? 'bg-blue-100 text-blue-800' : (${user.userType.name() == 'DISABLED'} ? 'bg-purple-100 text-purple-800' : 'bg-green-100 text-green-800')"
+                                          th:classappend="${#strings.equals(user.userType.name(), 'GENERAL')} ? 'bg-blue-100 text-blue-800' : 
+                                                        (${#strings.equals(user.userType.name(), 'DISABLED')} ? 'bg-purple-100 text-purple-800' : 'bg-green-100 text-green-800')"
                                          th:text="${user.userType.name()}">

또는 백엔드에서 스타일 클래스를 미리 계산하여 전달하는 것을 고려해보세요.

src/main/resources/templates/admin/wayblezone/wayble-zones.html (1)

108-111: 웨이블존 타입별 스타일링 로직 개선 필요

복잡한 조건부 클래스 할당이 가독성을 해치고 있습니다.

다음과 같이 개선할 수 있습니다:

                                    <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
-                                          th:classappend="${zone.zoneType.name() == 'RESTAURANT'} ? 'bg-green-100 text-green-800' : (${zone.zoneType.name() == 'CAFE'} ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-800')"
+                                          th:classappend="${#strings.equals(zone.zoneType.name(), 'RESTAURANT')} ? 'bg-green-100 text-green-800' : 
+                                                        (${#strings.equals(zone.zoneType.name(), 'CAFE')} ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-800')"
                                          th:text="${zone.zoneType.name()}">
src/main/resources/templates/admin/dashboard.html (1)

8-8: 사용하지 않는 Chart.js 라이브러리 제거 필요

Chart.js 라이브러리를 로드하고 있지만 템플릿에서 실제로 사용되지 않고 있습니다.

사용하지 않는 라이브러리는 제거하여 페이지 로딩 성능을 개선하세요:

-    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

차후 차트 기능이 필요할 때 다시 추가할 수 있습니다.

src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html (1)

185-189: 웨이블존 타입 옵션 표시 로직 개선 필요

복잡한 삼항 연산자로 타입별 한글명을 표시하고 있어 가독성이 떨어집니다.

백엔드에서 한글명을 포함한 DTO를 제공하거나, Thymeleaf의 메시지 기능을 활용하는 것이 좋습니다:

                                        <option th:each="type : ${waybleZoneTypes}" 
                                                th:value="${type}" 
-                                               th:text="${type == T(com.wayble.server.wayblezone.entity.WaybleZoneType).RESTAURANT ? '음식점' : 
-                                                        type == T(com.wayble.server.wayblezone.entity.WaybleZoneType).CAFE ? '카페' : 
-                                                        type == T(com.wayble.server.wayblezone.entity.WaybleZoneType).CONVENIENCE ? '편의점' : type}"></option>
+                                               th:text="#{wayblezone.type.__${type}__}"></option>

또는 백엔드에서 displayName과 함께 제공하는 방식을 고려해보세요.

src/main/resources/templates/admin/user/user-detail.html (1)

7-7: CDN에서 Tailwind CSS를 로드하는 것을 고려해보세요.

프로덕션 환경에서는 보안과 성능을 위해 로컬 호스팅을 고려해보세요.

src/main/resources/templates/admin/user/users.html (1)

205-215: 복잡한 페이지네이션 로직 단순화 고려

페이지 번호 계산이 복잡하고 이해하기 어렵습니다. 코드 가독성을 위해 백엔드에서 처리하는 것을 고려해보세요.

src/main/java/com/wayble/server/admin/controller/AdminController.java (1)

53-57: 로그인 실패 시 사용자명 로깅 주의

로그인 실패 시 사용자명을 로그에 기록하는 것은 보안상 민감할 수 있습니다. 브루트 포스 공격 탐지를 위한 것이라면 IP 주소나 세션 정보를 함께 기록하는 것을 고려해보세요.

-log.warn("관리자 로그인 실패 시도: {}", username);
+log.warn("관리자 로그인 실패 시도 - IP: {}, Username: {}", request.getRemoteAddr(), username);
src/main/resources/templates/admin/user/deleted-user-detail.html (1)

69-75: 복원 버튼에 추가 확인 절차 고려

중요한 작업인 사용자 복원에 대해 추가적인 보안 확인(예: 관리자 재인증)을 고려해보세요.

+data-requires-confirmation="true" 
+data-action-description="사용자 복원"
src/main/resources/templates/admin/wayblezone/wayble-zone-create.html (3)

281-287: 위도/경도 입력 필드의 step 속성 재검토 필요

현재 step="0.000000001"로 설정되어 있는데, 이는 실용적이지 않을 수 있습니다. 일반적으로 소수점 6자리 정도면 충분한 정확도를 제공합니다.

-                                           step="0.000000001"
+                                           step="0.000001"

Also applies to: 308-314


342-352: 이미지 URL 필드에 클라이언트 사이드 검증 추가 권장

현재 type="url"로 기본 브라우저 검증만 사용하고 있습니다. 사용자 경험 향상을 위해 추가 검증을 고려해보세요.

                                 <input type="url" 
                                        th:field="*{mainImageUrl}"
                                        id="mainImageUrl" 
+                                       pattern="https?://.*\.(jpg|jpeg|png|gif|webp)$"
+                                       title="올바른 이미지 URL을 입력하세요 (jpg, jpeg, png, gif, webp)"
                                        class="block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:ring-2 focus:ring-wayble-blue focus:border-wayble-blue transition-colors duration-200 placeholder-gray-400 text-sm"
                                        placeholder="https://example.com/image.jpg">

368-382: 버튼의 접근성 개선 필요

아이콘과 함께 텍스트가 있어 좋지만, 스크린 리더 사용자를 위한 추가 개선이 가능합니다.

                         <a href="https://github.com/admin/wayble-zones" 
+                           aria-label="목록으로 돌아가기"
                            class="inline-flex items-center px-6 py-3 border border-gray-300 text-base font-medium rounded-lg text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-wayble-blue transition-colors duration-200">
src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java (1)

28-33: 기본 페이지 크기 재검토 필요

기본 페이지 크기가 100으로 설정되어 있는데, 이는 너무 클 수 있습니다.

일반적으로 20-50 정도가 적절합니다:

-            @RequestParam(name = "size", defaultValue = "100") int size)
+            @RequestParam(name = "size", defaultValue = "20") int size)

또한 최대 페이지 크기 제한도 고려해보세요:

size = Math.min(size, 100); // 최대 100개로 제한

Also applies to: 46-51

src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java (1)

33-41: 주소 문자열 연결 로직 개선 필요

쿼리에서 복잡한 문자열 연결을 수행하는 것은 성능에 영향을 줄 수 있습니다.

다음 방안을 고려해보세요:

  1. 데이터베이스에 계산된 컬럼 추가
  2. DTO 생성 시 애플리케이션에서 처리
  3. Address 엔티티에 toFullAddress() 메서드가 있다면 활용
// DTO에서 처리하는 예시
.select(Projections.constructor(AdminWaybleZoneThumbnailDto.class,
    waybleZone.id,
    waybleZone.zoneName,
    waybleZone.zoneType,
    waybleZone.reviewCount,
    waybleZone.likes,
    waybleZone.rating,
    waybleZone.address, // Address 객체 전달
    waybleZone.facility
))
src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html (2)

345-353: 이미지 모달의 접근성 개선 필요

현재 이미지 모달에 키보드 네비게이션 지원이 없습니다.

ESC 키로 모달을 닫을 수 있도록 개선:

         function openImageModal(src) {
             document.getElementById('modalImage').src = src;
             document.getElementById('imageModal').classList.remove('hidden');
+            document.addEventListener('keydown', handleImageModalKeydown);
         }
         
         function closeImageModal() {
             document.getElementById('imageModal').classList.add('hidden');
             document.getElementById('modalImage').src = '';
+            document.removeEventListener('keydown', handleImageModalKeydown);
         }
+        
+        function handleImageModalKeydown(e) {
+            if (e.key === 'Escape') {
+                closeImageModal();
+            }
+        }

263-270: 동기화 상태 표시 로직 간소화 가능

현재 조건문이 복잡하게 작성되어 있습니다.

Thymeleaf의 변수 선언을 활용하여 가독성을 개선할 수 있습니다:

<dd class="mt-1" th:with="isSynced=${waybleZone.syncedAt != null and (waybleZone.lastModifiedAt == null or waybleZone.syncedAt.isAfter(waybleZone.lastModifiedAt))}">
    <span th:if="${isSynced}"
          class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
        동기화 완료
    </span>
    <span th:unless="${isSynced}"
          class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
        동기화 필요
    </span>
</dd>
src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java (2)

16-24: 페이지네이션 쿼리에 인덱스 추가 권장

created_at 필드로 정렬하고 있으므로, 쿼리 성능 향상을 위해 (deleted_at, created_at) 복합 인덱스 추가를 고려해주세요.


73-80: 리뷰 이미지 복원 쿼리 성능 최적화

JOIN 연산의 성능 향상을 위해 review.user_id에 인덱스가 있는지 확인하세요.

src/main/java/com/wayble/server/admin/service/AdminUserService.java (1)

221-230: 유틸리티 클래스로 이동 고려

convertToLong 메서드는 다른 서비스에서도 유용할 수 있으므로 공통 유틸리티 클래스로 이동하는 것을 고려해보세요.

src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java (1)

24-42: 기본 페이지 크기 조정 및 인증 로직 중복 개선 고려

현재 구현은 올바르지만 다음 사항들을 고려해보세요:

  1. 기본 페이지 크기가 100으로 설정되어 있어 성능상 부담이 될 수 있습니다.
  2. 세션 인증 체크 로직이 모든 메서드에서 반복됩니다.

기본 페이지 크기 조정:

-                          @RequestParam(defaultValue = "100") int size) {
+                          @RequestParam(defaultValue = "20") int size) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 404ecb9 and 7ecf278.

📒 Files selected for processing (42)
  • .github/workflows/cd-develop.yml (1 hunks)
  • build.gradle (1 hunks)
  • src/main/java/com/wayble/server/admin/controller/AdminController.java (1 hunks)
  • src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java (1 hunks)
  • src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java (1 hunks)
  • src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java (1 hunks)
  • src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java (1 hunks)
  • src/main/java/com/wayble/server/admin/dto/SystemStatusDto.java (1 hunks)
  • src/main/java/com/wayble/server/admin/dto/user/AdminUserDetailDto.java (1 hunks)
  • src/main/java/com/wayble/server/admin/dto/user/AdminUserPageDto.java (1 hunks)
  • src/main/java/com/wayble/server/admin/dto/user/AdminUserThumbnailDto.java (1 hunks)
  • src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneCreateDto.java (1 hunks)
  • src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java (1 hunks)
  • src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZonePageDto.java (1 hunks)
  • src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneThumbnailDto.java (1 hunks)
  • src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneUpdateDto.java (1 hunks)
  • src/main/java/com/wayble/server/admin/exception/AdminErrorCase.java (1 hunks)
  • src/main/java/com/wayble/server/admin/repository/AdminUserRepository.java (1 hunks)
  • src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepository.java (1 hunks)
  • src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryCustom.java (1 hunks)
  • src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java (1 hunks)
  • src/main/java/com/wayble/server/admin/service/AdminSystemService.java (1 hunks)
  • src/main/java/com/wayble/server/admin/service/AdminUserService.java (1 hunks)
  • src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java (1 hunks)
  • src/main/java/com/wayble/server/common/config/SecurityConfig.java (1 hunks)
  • src/main/java/com/wayble/server/common/entity/BaseEntity.java (2 hunks)
  • src/main/java/com/wayble/server/explore/service/WaybleZoneDocumentService.java (1 hunks)
  • src/main/java/com/wayble/server/user/repository/UserRepository.java (0 hunks)
  • src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java (3 hunks)
  • src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepository.java (1 hunks)
  • src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneRepositoryImpl.java (2 hunks)
  • src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneVisitLogRepository.java (1 hunks)
  • src/main/resources/templates/admin/dashboard.html (1 hunks)
  • src/main/resources/templates/admin/login.html (1 hunks)
  • src/main/resources/templates/admin/user/deleted-user-detail.html (1 hunks)
  • src/main/resources/templates/admin/user/deleted-users.html (1 hunks)
  • src/main/resources/templates/admin/user/user-detail.html (1 hunks)
  • src/main/resources/templates/admin/user/users.html (1 hunks)
  • src/main/resources/templates/admin/wayblezone/wayble-zone-create.html (1 hunks)
  • src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html (1 hunks)
  • src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html (1 hunks)
  • src/main/resources/templates/admin/wayblezone/wayble-zones.html (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/com/wayble/server/user/repository/UserRepository.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: java에서 같은 패키지 내의 클래스들은 import 구문 없이 서로를 참조할 수 있다. com.wayble.server.user.entity 패키지 내의 클래스들은 명시적인 im...
Learnt from: seung-in-Yoo
PR: Wayble-Project/wayble-spring#37
File: src/main/java/com/wayble/server/user/entity/User.java:61-62
Timestamp: 2025-07-13T15:08:59.318Z
Learning: Java에서 같은 패키지 내의 클래스들은 import 구문 없이 서로를 참조할 수 있다. com.wayble.server.user.entity 패키지 내의 클래스들은 명시적인 import 없이 사용 가능하다.

Applied to files:

  • src/main/java/com/wayble/server/admin/service/AdminUserService.java
🔇 Additional comments (43)
.github/workflows/cd-develop.yml (1)

5-5: 주석만 변경 – 기능적 영향 없음

브랜치명 주석 변경은 CI 동작에 영향을 주지 않습니다. 추가 조치 필요 없습니다.

src/main/java/com/wayble/server/common/config/SecurityConfig.java (1)

69-69: 공백 제거로 인한 변경 – 무시 가능

기능 변화가 없으므로 별도 피드백 없습니다.

src/main/java/com/wayble/server/common/entity/BaseEntity.java (2)

19-19: 데이터베이스 컬럼 매핑 명시화 승인

created_at과 updated_at 컬럼에 대한 명시적 매핑이 추가되어 데이터베이스 스키마와의 일관성이 향상되었습니다.

Also applies to: 23-23


33-35: 동시성 환경 검증 필요: 소프트 삭제/복구 메서드 호출 지점 확인

현재 코드베이스에서 softDelete()restore() 메서드는 호출되는 위치를 찾을 수 없습니다.
동시성 환경에서 이 메서드들이 실제로 사용될 지점에서 트랜잭션, 잠금 처리, 동시성 테스트가 적절히 적용되었는지 직접 검토해주세요.

  • 호출 위치: ast-grep 검색 결과 없음
src/main/java/com/wayble/server/wayblezone/repository/WaybleZoneVisitLogRepository.java (1)

16-18: 트랜잭션 처리 및 호출 위치 검증 요청

WaybleZoneVisitLogRepository의 deleteByZoneId 메서드는 올바르게 구현되었습니다. 다만, 아래 사항을 반드시 확인해주세요:

  • 호출 위치: AdminWaybleZoneService 또는 관련 서비스에서 deleteByZoneId를 실제로 호출하고 있는지
  • 트랜잭션 범위: 호출하는 서비스/메서드에 @Transactional 어노테이션이 적용되어 있는지
  • 전파 전략: 트랜잭션 전파(propagation) 설정이 적절한지 (REQUIRED, REQUIRES_NEW 등)

위 항목을 점검 후 결과를 공유해주시면 감사하겠습니다.

src/main/java/com/wayble/server/admin/dto/user/AdminUserThumbnailDto.java (1)

12-13: 민감한 개인정보 노출에 주의하세요.

이메일과 생년월일은 민감한 개인정보입니다. 관리자 페이지에서 이러한 정보의 노출 범위와 접근 권한을 적절히 제한하고 있는지 확인해주세요.

src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepository.java (1)

6-7: 좋은 설계입니다!

JpaRepository와 커스텀 인터페이스를 적절히 분리한 깔끔한 설계입니다. 표준 JPA 작업과 커스텀 작업의 관심사가 잘 분리되어 있습니다.

src/main/java/com/wayble/server/admin/exception/AdminErrorCase.java (1)

11-12: 에러 케이스 정의가 잘 되어있습니다.

에러 코드가 체계적으로 정의되어 있고, 한국어 메시지로 일관성 있게 작성되었습니다.

src/main/java/com/wayble/server/admin/dto/SystemStatusDto.java (1)

3-8: 시스템 상태 DTO가 잘 설계되었습니다.

boolean 필드들로 시스템 상태를 명확하게 표현했고, record를 사용한 불변 객체 설계가 적절합니다.

src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneThumbnailDto.java (1)

6-15: 웨이블존 썸네일 DTO가 잘 구성되었습니다.

관리자 화면에서 필요한 핵심 정보들이 적절히 선택되어 있고, record를 사용한 불변 객체 설계가 좋습니다.

src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZonePageDto.java (1)

20-22: 페이지네이션 로직 검증 완료

총 페이지 수 계산과 다음/이전 페이지 존재 여부 로직이 올바르게 구현되었습니다.

src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneCreateDto.java (1)

9-39: 검증 로직이 잘 구현됨

Jakarta Bean Validation을 사용한 입력 검증이 포괄적으로 구현되었습니다:

  • 필수 필드에 대한 적절한 @NotBlank/@NotNull 적용
  • 위경도 좌표의 지리학적 범위 검증 (-90.0~90.0, -180.0~180.0)
  • 한국어 오류 메시지 제공
  • 선택적 필드들의 적절한 구분
src/main/java/com/wayble/server/admin/dto/user/AdminUserPageDto.java (2)

5-15: 표준 페이지네이션 구조로 잘 구현됨

이 DTO는 완전한 페이지네이션 메타데이터를 제공합니다:

  • 표준 필드명 사용 (page, size)
  • first, last 플래그로 페이지 경계 표시
  • 네비게이션 상태 플래그 (hasNext, hasPrevious)

다른 페이지네이션 DTO들도 이 구조를 따르는 것이 좋겠습니다.


17-28: 페이지네이션 계산 로직 정확함

정적 팩토리 메서드의 페이지네이션 계산이 올바르게 구현되었습니다:

  • 총 페이지 수 계산 방식 적절
  • 경계 조건 처리 정확
src/main/java/com/wayble/server/wayblezone/entity/WaybleZone.java (4)

3-3: 새로운 import 추가 확인

관리자 DTO를 위한 import가 적절히 추가되었습니다.


106-130: 관리자 업데이트 메서드들이 일관성 있게 구현됨

모든 업데이트 메서드가 일관된 패턴을 따릅니다:

  • 필드 값 변경
  • markAsModified() 호출로 ES 동기화 추적

이는 데이터 일관성과 동기화를 보장하는 좋은 패턴입니다.


161-185: 팩토리 메서드 구현 확인 필요

fromAdminDto 메서드에서 mainImageUrlnull로 설정되어 있습니다 (라인 178). DTO에서 제공되는 dto.mainImageUrl()을 사용해야 하는지 확인이 필요합니다.

-                .mainImageUrl(null)
+                .mainImageUrl(dto.mainImageUrl())

이 변경이 의도된 것인지 확인해 주세요.


162-171: Address 객체 생성 로직 적절함

DTO의 주소 정보를 사용하여 임베디드 Address 객체를 올바르게 구성했습니다.

src/main/java/com/wayble/server/admin/dto/user/AdminUserDetailDto.java (1)

10-26: 포괄적인 사용자 상세 정보 DTO

관리자가 필요로 하는 사용자 정보를 모두 포함한 잘 설계된 DTO입니다:

  • 사용자 식별 정보 (id, username, nickname, email)
  • 개인 정보 (birthDate, gender, 접근성 정보)
  • 인증 및 역할 정보 (loginType, userType)
  • 활동 지표 (reviewCount, userPlaceCount)
  • 타임스탬프 정보

필드 타입과 구조가 적절하며 읽기 전용 데이터 전송에 최적화되어 있습니다.

src/main/java/com/wayble/server/admin/service/AdminSystemService.java (2)

20-29: Elasticsearch 헬스체크 구현이 적절합니다

Elasticsearch 클러스터 상태를 정확히 확인하고 RED 상태가 아닌 경우를 정상으로 판단하는 로직이 올바릅니다. 예외 처리와 로깅도 적절히 구현되어 있습니다.


31-38: 데이터베이스 헬스체크 구현이 적절합니다

Connection의 isValid() 메서드를 사용하여 5초 타임아웃으로 연결 상태를 확인하는 방식이 적절합니다. try-with-resources를 사용한 리소스 관리도 올바릅니다.

src/main/resources/templates/admin/dashboard.html (1)

220-236: 시스템 상태 표시 로직이 적절합니다

시스템 상태를 동적으로 표시하는 Thymeleaf 조건문이 올바르게 구현되어 있습니다. 상태에 따른 색상 구분과 텍스트 표시가 적절합니다.

src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html (2)

300-306: 위도 입력 필드의 유효성 검증이 적절합니다

위도 범위(-90 ~ 90)와 소수점 정밀도(0.000000001)가 올바르게 설정되어 있습니다. GPS 좌표의 정확한 표현을 위한 적절한 설정입니다.


327-333: 경도 입력 필드의 유효성 검증이 적절합니다

경도 범위(-180 ~ 180)와 소수점 정밀도가 위도와 일관성 있게 설정되어 있습니다.

src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneUpdateDto.java (2)

9-42: DTO 구조와 검증이 잘 구현되어 있습니다.

레코드 구조가 깔끔하고 필수 필드에 대한 검증이 적절히 적용되어 있습니다. 특히 위도와 경도에 대한 범위 검증이 정확합니다.


47-62: 팩토리 메서드 구현이 깔끔합니다.

fromDetailDto 메서드가 DetailDto에서 UpdateDto로의 변환을 명확하게 처리하고 있어 코드 재사용성을 높입니다.

src/main/resources/templates/admin/user/user-detail.html (1)

65-65: 검증 완료: XSS 취약점 없음

  • user-detail.html 전체에서 th:utext(비-이스케이프 출력)가 전혀 사용되지 않으며, 오직 th:text만 사용되어 있습니다.
  • Thymeleaf의 기본 HTML 이스케이핑이 적용되므로, 사용자 닉네임은 안전하게 처리됩니다.

따라서 현재 구현에서는 XSS 취약점이 없습니다.

src/main/java/com/wayble/server/admin/controller/AdminController.java (2)

28-32: 설정에서 관리자 자격 증명 주입이 적절합니다.

@Value 어노테이션을 사용하여 외부 설정에서 관리자 자격 증명을 주입하는 것이 좋은 방식입니다.


65-67: 세션 기반 인증 로직이 간단하고 명확합니다.

세션에서 로그인 상태를 확인하는 로직이 적절합니다.

src/main/resources/templates/admin/user/deleted-user-detail.html (2)

306-322: 복원 기능의 JavaScript 구현이 잘 되어 있습니다.

동적으로 폼을 생성하고 CSRF 토큰을 처리하는 로직이 적절합니다. 보안을 고려한 구현입니다.


299-304: 모달 닫기 이벤트 처리가 안전합니다.

이벤트 버블링을 방지하여 의도하지 않은 모달 닫기를 방지하는 로직이 좋습니다.

src/main/resources/templates/admin/wayblezone/wayble-zone-create.html (1)

50-54: CSRF 비활성화로 인해 로그아웃 폼 수정 불필요

SecurityConfig.java에서 .csrf(csrf -> csrf.disable()) 호출로 CSRF 보호가 전역 비활성화되어 있어, 별도의 토큰 추가 없이도 로그아웃 폼이 정상 동작합니다.

  • src/main/java/com/wayble/server/common/config/SecurityConfig.java:37 – .csrf(disable()) 적용
src/main/java/com/wayble/server/admin/service/AdminWaybleZoneService.java (3)

24-33: 서비스 클래스 구조가 잘 설계되었습니다

클래스 레벨에서 @Transactional(readOnly = true)를 사용하고 쓰기 작업에서만 오버라이드하는 것은 좋은 패턴입니다.


91-100: Address 업데이트 로직이 적절합니다

새로운 Address 객체를 생성하여 업데이트하는 방식은 불변 값 객체 패턴에 적합합니다.


39-50: 페이징 구현이 적절합니다

페이징 로직이 명확하고 DTO 변환도 잘 구성되어 있습니다.

src/main/java/com/wayble/server/admin/repository/AdminWaybleZoneRepositoryImpl.java (1)

74-77: 컬렉션 지연 로딩 전략이 적절합니다

필요시 별도 쿼리로 컬렉션을 로드하는 전략은 N+1 문제를 방지하는 좋은 방법입니다.

src/main/java/com/wayble/server/admin/service/AdminUserService.java (1)

124-160: 트랜잭션 처리가 잘 구현되었습니다

사용자 복원 로직이 트랜잭션으로 적절히 보호되고 있으며, 관련 엔티티들도 함께 복원하는 점이 좋습니다. 로깅도 충실하게 되어 있습니다.

src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZoneDetailDto.java (1)

10-59: 레코드를 활용한 DTO 구현이 우수합니다

Java 레코드를 사용하여 불변 DTO를 구현한 점이 좋습니다. 중첩 레코드를 통해 관련 데이터를 구조화한 것도 적절합니다.

src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneViewController.java (1)

155-170: ID 검증 로직이 적절합니다

URL 경로의 ID와 DTO의 ID를 일치시키는 검증 로직이 보안상 좋은 구현입니다.

src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java (4)

1-23: LGTM! 클래스 구조와 의존성 주입이 적절합니다.

Spring MVC 컨트롤러의 표준 구조를 잘 따르고 있으며, Lombok을 활용한 생성자 주입이 올바르게 구현되어 있습니다.


64-82: 구현이 일관성 있게 되어 있습니다.

삭제된 사용자 목록 조회 로직이 일반 사용자 조회와 일관성 있게 구현되어 있어 좋습니다. 앞서 언급한 페이지 크기와 인증 로직 중복 개선사항이 여기에도 적용됩니다.


84-102: 삭제된 사용자 상세 조회 로직이 잘 구현되었습니다.

Optional을 활용한 null 안전성 처리와 일관성 있는 에러 처리가 잘 되어 있습니다.


104-124: 사용자 복원 기능이 우수하게 구현되었습니다.

상태 변경 작업에 대한 적절한 HTTP 메서드 사용, 예외 처리, 플래시 메시지를 통한 사용자 피드백, 그리고 상황에 맞는 리다이렉트 처리가 모범적으로 구현되어 있습니다.

HttpSession session,
Model model
) {
if (adminUsername.equals(username) && adminPassword.equals(password)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

평문 비밀번호 비교는 보안상 위험합니다.

평문으로 비밀번호를 비교하고 있습니다. 프로덕션 환경에서는 해시된 비밀번호를 사용해야 합니다.

다음과 같이 개선하세요:

-if (adminUsername.equals(username) && adminPassword.equals(password)) {
+if (adminUsername.equals(username) && passwordEncoder.matches(password, adminPassword)) {

그리고 PasswordEncoder 의존성을 추가하세요:

private final PasswordEncoder passwordEncoder;
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/admin/controller/AdminController.java at line
50, the code compares plaintext passwords directly, which is insecure. To fix
this, replace the plaintext password comparison with a hashed password check
using a PasswordEncoder. Add a PasswordEncoder dependency to the class and use
its matches method to compare the raw password with the stored hashed password
securely.

Comment on lines +35 to +38
@GetMapping("/{userId}")
public CommonResponse<Optional<AdminUserDetailDto>> findUserById(@PathVariable("userId") long userId) {
return CommonResponse.success(adminUserService.findUserById(userId));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

REST API에서 Optional 반환 재고 필요

REST API 응답에서 Optional을 직접 반환하는 것은 일반적이지 않습니다. 클라이언트 입장에서는 명확한 응답 구조가 더 유용합니다.

-    public CommonResponse<Optional<AdminUserDetailDto>> findUserById(@PathVariable("userId") long userId) {
-        return CommonResponse.success(adminUserService.findUserById(userId));
+    public CommonResponse<AdminUserDetailDto> findUserById(@PathVariable("userId") long userId) {
+        return adminUserService.findUserById(userId)
+            .map(CommonResponse::success)
+            .orElseThrow(() -> new ApplicationException(AdminErrorCase.USER_NOT_FOUND));
     }

Also applies to: 53-56

🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/admin/controller/user/AdminUserController.java
around lines 35 to 38 and 53 to 56, the controller methods return Optional
wrapped inside CommonResponse, which is not a good practice for REST APIs.
Instead, modify the methods to check if the Optional contains a value; if
present, return the value inside CommonResponse.success, otherwise return an
appropriate error response or a CommonResponse indicating the user was not
found. This will provide a clearer and more standard API response structure to
clients.

Comment on lines +29 to +30
if (session.getAttribute("adminLoggedIn") == null) {
return "redirect:/admin";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

인증 로직 중복 제거를 위한 리팩토링 고려

모든 메서드에서 동일한 세션 인증 체크 로직이 반복됩니다. 다음 방법들을 고려해보세요:

방법 1: 인터셉터 사용

@Component
public class AdminAuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        HttpSession session = request.getSession();
        if (session.getAttribute("adminLoggedIn") == null) {
            response.sendRedirect("/admin");
            return false;
        }
        return true;
    }
}

방법 2: 메서드 레벨 보안
Spring Security의 @PreAuthorize 또는 커스텀 애노테이션을 활용하여 인증 로직을 중앙화할 수 있습니다.

Also applies to: 47-48, 69-70, 87-88, 109-110

🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java
around lines 29-30, 47-48, 69-70, 87-88, and 109-110, the session authentication
check is duplicated in multiple methods. To fix this, refactor by removing these
repeated checks and implement a centralized authentication mechanism such as a
Spring MVC interceptor that checks the session attribute "adminLoggedIn" before
controller methods execute, redirecting unauthorized requests to "/admin".
Alternatively, use Spring Security annotations like @PreAuthorize or a custom
security annotation to handle authentication at the method level, eliminating
redundant code and improving maintainability.

Comment on lines +44 to +62
@GetMapping("/{id}")
public String getUserDetail(HttpSession session, Model model, @PathVariable Long id) {
// 로그인 확인
if (session.getAttribute("adminLoggedIn") == null) {
return "redirect:/admin";
}

Optional<AdminUserDetailDto> userOpt = adminUserService.findUserById(id);
if (userOpt.isEmpty()) {
return "redirect:/admin/user/users?error=notfound";
}

model.addAttribute("user", userOpt.get());
model.addAttribute("adminUsername", session.getAttribute("adminUsername"));

log.debug("사용자 상세 조회 - ID: {}", id);

return "admin/user/user-detail";
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

리다이렉트 URL 오류 수정 필요

사용자를 찾을 수 없을 때의 리다이렉트 URL이 잘못되었습니다.

-            return "redirect:/admin/user/users?error=notfound";
+            return "redirect:/admin/users?error=notfound";
📝 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("/{id}")
public String getUserDetail(HttpSession session, Model model, @PathVariable Long id) {
// 로그인 확인
if (session.getAttribute("adminLoggedIn") == null) {
return "redirect:/admin";
}
Optional<AdminUserDetailDto> userOpt = adminUserService.findUserById(id);
if (userOpt.isEmpty()) {
return "redirect:/admin/user/users?error=notfound";
}
model.addAttribute("user", userOpt.get());
model.addAttribute("adminUsername", session.getAttribute("adminUsername"));
log.debug("사용자 상세 조회 - ID: {}", id);
return "admin/user/user-detail";
}
@GetMapping("/{id}")
public String getUserDetail(HttpSession session, Model model, @PathVariable Long id) {
// 로그인 확인
if (session.getAttribute("adminLoggedIn") == null) {
return "redirect:/admin";
}
Optional<AdminUserDetailDto> userOpt = adminUserService.findUserById(id);
if (userOpt.isEmpty()) {
return "redirect:/admin/users?error=notfound";
}
model.addAttribute("user", userOpt.get());
model.addAttribute("adminUsername", session.getAttribute("adminUsername"));
log.debug("사용자 상세 조회 - ID: {}", id);
return "admin/user/user-detail";
}
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/admin/controller/user/AdminUserViewController.java
lines 44 to 62, the redirect URL when a user is not found is incorrect. Update
the redirect URL in the condition where userOpt.isEmpty() to the correct path
that properly handles the error scenario, ensuring it directs to the intended
user listing or error page.

Comment on lines +19 to +23
@RestController
@RequiredArgsConstructor
@Validated
@RequestMapping("/api/v1/admin/wayble-zones")
public class AdminWaybleZoneController {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

관리자 API 보안 설정 누락

관리자 전용 REST API이지만 인증/인가 메커니즘이 없습니다. Spring Security나 커스텀 인터셉터를 통해 보안을 강화해야 합니다.

다음과 같은 보안 설정을 추가하세요:

@RestController
@RequiredArgsConstructor
@Validated
@RequestMapping("/api/v1/admin/wayble-zones")
@PreAuthorize("hasRole('ADMIN')")  // Spring Security 사용 시
public class AdminWaybleZoneController {
🤖 Prompt for AI Agents
In
src/main/java/com/wayble/server/admin/controller/wayblezone/AdminWaybleZoneController.java
around lines 19 to 23, the controller lacks security annotations to restrict
access to admin users. Add the @PreAuthorize("hasRole('ADMIN')") annotation
above the class declaration to enforce that only users with the ADMIN role can
access these endpoints, assuming Spring Security is configured in the project.

Comment on lines +7 to +19
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
'wayble-blue': '#3B82F6',
'wayble-dark': '#1E293B',
}
}
}
}
</script>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

프로덕션 환경을 위한 Tailwind CSS 최적화 필요

현재 Tailwind CDN을 사용하고 있는데, 이는 개발 환경에는 적합하지만 프로덕션 환경에서는 성능 문제를 야기할 수 있습니다.

프로덕션 환경에서는 다음과 같이 빌드 시스템을 통해 Tailwind CSS를 최적화하는 것을 권장합니다:

  1. PostCSS와 Tailwind CLI를 사용하여 사용하지 않는 스타일을 제거
  2. CSS 파일을 별도로 빌드하여 정적 리소스로 제공
  3. 빌드된 CSS 파일을 CDN에 호스팅하여 캐싱 활용
🤖 Prompt for AI Agents
In src/main/resources/templates/admin/wayblezone/wayble-zone-create.html around
lines 7 to 19, the Tailwind CSS is included via CDN which is not optimized for
production. To fix this, remove the CDN script and inline Tailwind config, then
set up a build process using PostCSS and Tailwind CLI to generate a minimized
CSS file containing only used styles. Serve this built CSS as a static resource
and host it on a CDN for caching and better performance in production.

Comment on lines +371 to +378
const csrfToken = document.querySelector('meta[name="_csrf"]');
if (csrfToken) {
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = '_csrf';
csrfInput.value = csrfToken.getAttribute('content');
form.appendChild(csrfInput);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

CSRF 토큰 처리 로직 개선 필요

CSRF 토큰이 없을 때의 처리가 누락되어 있습니다.

             const csrfToken = document.querySelector('meta[name="_csrf"]');
             if (csrfToken) {
                 const csrfInput = document.createElement('input');
                 csrfInput.type = 'hidden';
                 csrfInput.name = '_csrf';
                 csrfInput.value = csrfToken.getAttribute('content');
                 form.appendChild(csrfInput);
+            } else {
+                // Thymeleaf의 기본 CSRF 토큰 확인
+                const thymeleafToken = document.querySelector('input[name="_csrf"]');
+                if (thymeleafToken) {
+                    form.appendChild(thymeleafToken.cloneNode(true));
+                }
             }
📝 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
const csrfToken = document.querySelector('meta[name="_csrf"]');
if (csrfToken) {
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = '_csrf';
csrfInput.value = csrfToken.getAttribute('content');
form.appendChild(csrfInput);
}
const csrfToken = document.querySelector('meta[name="_csrf"]');
if (csrfToken) {
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = '_csrf';
csrfInput.value = csrfToken.getAttribute('content');
form.appendChild(csrfInput);
} else {
// Thymeleaf의 기본 CSRF 토큰 확인
const thymeleafToken = document.querySelector('input[name="_csrf"]');
if (thymeleafToken) {
form.appendChild(thymeleafToken.cloneNode(true));
}
}
🤖 Prompt for AI Agents
In src/main/resources/templates/admin/wayblezone/wayble-zone-detail.html around
lines 371 to 378, the code appends a CSRF token input to the form only if the
token meta tag exists, but it lacks handling for the case when the CSRF token is
missing. Add proper error handling or fallback logic for when the CSRF token
meta tag is not found, such as logging an error or preventing form submission to
ensure security.

Comment on lines +115 to +117
<form th:action="@{/admin/wayble-zones/{id}/edit(id=${updateDto.id})}" th:object="${updateDto}" method="post" class="px-8 py-6">
<!-- Hidden field for ID -->
<input type="hidden" th:field="*{id}">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

CSRF 토큰 보안 검증 필요

폼에 CSRF 토큰이 명시적으로 포함되지 않았습니다. Spring Security를 사용하는 경우 자동으로 처리되지만 명시적 확인이 필요합니다.

CSRF 보호가 제대로 설정되어 있는지 확인해보세요:

필요시 다음과 같이 명시적으로 추가할 수 있습니다:

<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>

🏁 Script executed:

#!/bin/bash
# Spring Security CSRF 설정 확인
rg -A 5 -B 5 "csrf|CsrfToken" --type java

Length of output: 1301


SecurityConfig에서 CSRF 보호가 비활성화되어 있습니다

현재 src/main/java/com/wayble/server/common/config/SecurityConfig.javasecurityFilterChain 설정에서

.csrf(csrf -> csrf.disable())

로 CSRF 검증이 전역에서 꺼져 있어, POST/PUT/DELETE 요청이 토큰 없이 처리됩니다.
이와 함께 Thymeleaf 폼(wayble-zone-edit.html)에도 CSRF 토큰이 포함되어 있지 않아 공격에 매우 취약한 상태입니다.

수정 제안:

  • SecurityConfig에서 .csrf(csrf -> csrf.disable()) 부분을 제거하거나, 특정 API 경로만 비활성화하도록 재구성하세요.
  • 폼에 CSRF 토큰 필드를 추가하세요:
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
  • 변경 파일 위치:
    • src/main/java/com/wayble/server/common/config/SecurityConfig.java
    • src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html
🤖 Prompt for AI Agents
In src/main/resources/templates/admin/wayblezone/wayble-zone-edit.html around
lines 115 to 117, the form lacks a CSRF token field, making it vulnerable to
CSRF attacks. Add a hidden input field inside the form with th:name set to
${_csrf.parameterName} and th:value set to ${_csrf.token} to include the CSRF
token. Additionally, in
src/main/java/com/wayble/server/common/config/SecurityConfig.java, remove or
modify the .csrf(csrf -> csrf.disable()) configuration to enable CSRF protection
globally or selectively for specific API paths.

새 웨이블존 생성
</a>
<div class="text-sm text-gray-500">
<span th:text="${pageData.currentPage + 1}"></span> / <span th:text="${pageData.totalPages}"></span> 페이지

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

페이지네이션 속성 불일치

Line 75에서 pageData.currentPage를 사용하고 있으나, 삭제된 사용자 템플릿에서는 pageData.page를 사용하고 있어 일관성이 없습니다.

백엔드 DTO의 페이지네이션 속성명을 확인하여 일관성을 맞춰야 합니다.


🏁 Script executed:

#!/bin/bash
# 페이지네이션 DTO 속성명 확인
rg -A 10 -B 5 "class.*PageData|class.*Page.*Dto" --type java

Length of output: 62


🏁 Script executed:

#!/bin/bash
# 페이지네이션 DTO 클래스 확인: PageData, PageDto 등
rg -n "class .*PageData" --type java
rg -n "record .*PageData" --type java
rg -n "class .*Page.*Dto" --type java
rg -n "record .*Page.*Dto" --type java

Length of output: 383


페이지네이션 DTO 레코드 선언부 필드 확인


🏁 Script executed:

#!/bin/bash
# AdminWaybleZonePageDto 필드 확인
rg -A5 "public record AdminWaybleZonePageDto" src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZonePageDto.java

# AdminUserPageDto 필드 확인
rg -A5 "public record AdminUserPageDto" src/main/java/com/wayble/server/admin/dto/user/AdminUserPageDto.java

Length of output: 572


페이지네이션 프로퍼티 네이밍 일관성 유지 필요

DTO와 템플릿 간에 참조하는 속성명이 일치하지 않습니다. 아래와 같이 확인되니, 하나의 네이밍으로 통합해주세요.

  • DTO 정의

    • AdminWaybleZonePageDto (src/main/java/com/wayble/server/admin/dto/wayblezone/AdminWaybleZonePageDto.java)
      • content, currentPage, pageSize, totalElements, totalPages
    • AdminUserPageDto (src/main/java/com/wayble/server/admin/dto/user/AdminUserPageDto.java)
      • content, page, size, totalElements, totalPages
  • 템플릿 참조

    • src/main/resources/templates/admin/wayblezone/wayble-zones.html (Line 75): pageData.currentPage
    • 사용자 템플릿 (삭제된 버전): pageData.page

→ DTO 또는 템플릿 중 한쪽을 선택해 currentPagepage, pageSizesize 네이밍을 통일해주세요.

🤖 Prompt for AI Agents
In src/main/resources/templates/admin/wayblezone/wayble-zones.html at line 75,
the pagination property names used in the template do not match those in the
DTOs, causing inconsistency. Choose either to use 'currentPage' and 'pageSize'
or 'page' and 'size' consistently across both the DTOs and the template. Update
the template's references to pagination properties to align with the chosen
naming convention, ensuring that 'pageData.currentPage' and 'pageData.pageSize'
or their counterparts are used uniformly.

Comment on lines +99 to +101
<div th:each="zone : ${pageData.content}"
class="border border-gray-200 rounded-lg p-6 hover:shadow-md transition-shadow duration-200 cursor-pointer"
th:onclick="'window.location.href=\'/admin/wayble-zones/' + ${zone.zoneId} + '\''">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

JavaScript onclick 보안 취약점 동일 이슈

삭제된 사용자 템플릿과 동일한 보안 문제가 있습니다. JavaScript onclick 대신 적절한 링크를 사용해야 합니다.

-                    <div th:each="zone : ${pageData.content}" 
-                         class="border border-gray-200 rounded-lg p-6 hover:shadow-md transition-shadow duration-200 cursor-pointer"
-                         th:onclick="'window.location.href=\'/admin/wayble-zones/' + ${zone.zoneId} + '\''">
+                    <a th:each="zone : ${pageData.content}" 
+                       th:href="@{/admin/wayble-zones/{id}(id=${zone.zoneId})}"
+                       class="block border border-gray-200 rounded-lg p-6 hover:shadow-md transition-shadow duration-200 text-decoration-none">

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/resources/templates/admin/wayblezone/wayble-zones.html around lines
99 to 101, the use of JavaScript onclick for navigation creates a security
vulnerability. Replace the onclick attribute with a proper anchor tag (<a>) that
uses the href attribute for navigation to ensure safer and more accessible
links. Wrap the content inside the anchor tag and remove the onclick attribute
entirely.

@seung-in-Yoo

seung-in-Yoo commented Aug 2, 2025

Copy link
Copy Markdown
Member

헐 이거 작업하는데 꽤 오래걸렸을거같은데요...? (코드래빗의 폭풍 리뷰 ㄷㄷ) 디자인 깔끔하고 너무 좋아요 ㅋㅋㅋㅋㅋㅋㅎㅎ 수고하셨습니다!!

@KiSeungMin KiSeungMin merged commit 0f9d4a4 into develop Aug 3, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants