Skip to content

[BE] [FEAT] 관리자 회원 관리 기능 추가#228

Open
Yooonjeong wants to merge 38 commits intomainfrom
20260210_#206_관리자_회원_관리_기능_추가

Hidden character warning

The head ref may contain hidden characters: "20260210_#206_\uad00\ub9ac\uc790_\ud68c\uc6d0_\uad00\ub9ac_\uae30\ub2a5_\ucd94\uac00"
Open

[BE] [FEAT] 관리자 회원 관리 기능 추가#228
Yooonjeong wants to merge 38 commits intomainfrom
20260210_#206_관리자_회원_관리_기능_추가

Conversation

@Yooonjeong
Copy link
Contributor

@Yooonjeong Yooonjeong commented Feb 13, 2026

Summary by CodeRabbit

  • 새로운 기능

    • Excel 파일로 사용자 일괄 업로드·동기화 기능 추가
    • 관리자가 사용자를 선배(SENIOR)로 승격 가능 및 관리자용 사용자 관리 엔드포인트 추가
  • 개선 사항

    • 관리자용 사용자 조회·검색·필터링 결과 형식 및 응답 개선
    • 업로드 파일 형식·구조·빈파일 검증 및 상세 오류 코드 추가
    • 등급·직책·성별 문자열 해석 로직 강화로 데이터 수집 정확도 향상
    • 탈퇴 회원 로그인 차단 및 상태/권한 변경 기능 추가
  • 데이터 모델/무결성

    • 계정 테이블에 소유자·유형 기반 중복 제약 추가

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

엑셀(.xlsx) 기반 사용자 동기화(검증·파싱·행 단위 매핑·DB 동기화)와 관리자용 사용자 관리 API(필터 조회, 상태·역할 변경, 선배 승격, 강제 삭제)를 추가하고 Apache POI 의존성을 도입했습니다.

Changes

Cohort / File(s) Summary
의존성
backend/build.gradle
Apache POI (org.apache.poi:poi-ooxml:5.5.1) 추가
Admin 컨트롤러
backend/src/main/java/org/sejongisc/backend/admin/controller/AdminUserController.java
UserService 대신 AdminUserService 주입; 엑셀 업로드가 ExcelSyncResponse 반환; 조회·상태/역할 변경·선배 승격·삭제를 adminUserService로 위임
Admin DTOs
backend/src/main/java/org/sejongisc/backend/admin/dto/AdminUserRequest.java, .../AdminUserResponse.java, .../ExcelSyncResponse.java, .../UserExcelRow.java
AdminUserRequestrecord로 변경(필드: keyword, generation, role, status); AdminUserResponse, ExcelSyncResponse, UserExcelRow 레코드 추가
Admin 저장소
backend/src/main/java/org/sejongisc/backend/admin/repository/AdminUserRepository.java
관리자용 JPQL 필터 쿼리 추가(findAllByAdminFilter)—AdminUserResponse로 프로젝션
Admin 서비스
backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserService.java, .../AdminUserSyncService.java
파일 유효성 검사·엑셀 파싱·행 검증 로직 추가; syncUsersFromExcel 구현과 AdminUserSyncService.syncMemberData로 실제 동기화 수행(사용자 생성/업데이트, 계정 생성, 가입 보상 포인트 발급, 로깅)
유저 도메인 변경
backend/src/main/java/org/sejongisc/backend/user/entity/User.java, .../Grade.java, .../Role.java, .../Gender.java
isNewMember 제거, Grade grade 추가, applyExcelData·canLogin 추가; Grade, Role, Gender에 문자열 파싱 헬퍼 추가
리포지토리·엔티티·예외 코드
backend/src/main/java/org/sejongisc/backend/user/repository/UserRepository.java, backend/src/main/java/org/sejongisc/backend/point/entity/Account.java, backend/src/main/java/org/sejongisc/backend/common/exception/ErrorCode.java, backend/src/main/java/org/sejongisc/backend/user/entity/UserStatus.java
findAllByStatus(UserStatus) 추가; Account에 DB 유니크 제약 추가; Excel 업로드 관련 에러 코드(INVALID_FILE_FORMAT, INVALID_EXCEL_STRUCTURE, EMPTY_FILE) 및 USER_WITHDRAWN 추가; UserStatus에서 PENDING 제거
사용자 서비스·인증 변경
backend/src/main/java/org/sejongisc/backend/user/service/UserService.java, backend/src/main/java/org/sejongisc/backend/common/auth/service/AuthService.java
UserServiceupdateUserStatus/updateUserRole/promoteToSenior 추가 및 내부 유저 조회 헬퍼; AuthService에서 탈퇴 사용자 로그인 차단(canLogin) 및 비밀번호 비교 단순화

Sequence Diagrams

sequenceDiagram
    actor Admin
    participant Controller as AdminUserController
    participant Service as AdminUserService
    participant Sync as AdminUserSyncService
    participant Repo as AdminUserRepository
    participant UserRepo as UserRepository
    participant Account as AccountService
    participant Ledger as PointLedgerService

    Admin->>Controller: uploadMemberExcel(file)
    Controller->>Service: syncUsersFromExcel(file)
    Service->>Service: validateFile(file)\nparse Excel rows
    Service->>Sync: syncMemberData(rows)
    Sync->>UserRepo: deactivateActiveNonAdminUsers()
    loop for each row
        Sync->>UserRepo: findByStudentId(studentId)
        alt user exists
            Sync->>UserRepo: save(updatedUser)
        else
            Sync->>UserRepo: save(newUser)
            Sync->>Account: createAccount(newUser)
            Sync->>Ledger: issueSignupReward(newUser)
        end
    end
    Sync-->>Service: ExcelSyncResponse(created, updated)
    Service-->>Controller: ExcelSyncResponse
    Controller-->>Admin: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • discipline24
  • cksdid202

Poem

🐰 엑셀 한 장 들고 달려왔지
학생ID 줄 맞추고 이름도 반짝
컨트롤러가 전하고 서비스가 손보네
새 계정엔 포인트 톡, 업데이트는 척척
토끼가 박수쳐, 데이터가 환히 웃네 ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ⚠️ Unable to check for merge conflicts: Invalid branch name format
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 관리자 회원 관리 기능 추가라는 명확한 의도를 표현하며, 변경 사항의 핵심(관리자용 Excel 동기화, 사용자 관리 엔드포인트 추가)을 정확히 반영합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 20260210_#206_관리자_회원_관리_기능_추가

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@backend/build.gradle`:
- Around line 98-100: The POI dependency org.apache.poi:poi-ooxml is pinned at a
vulnerable version (5.3.0); update the dependency declaration that contains
"org.apache.poi:poi-ooxml:5.3.0" to a fixed version (at least 5.4.0, preferably
5.5.1) to remediate CVE-2025-31672 and ensure compatibility with Java 21 /
Spring Boot 3.5.5.

In
`@backend/src/main/java/org/sejongisc/backend/admin/controller/AdminUserController.java`:
- Around line 54-58: The `@PreAuthorize` annotation in AdminUserController on
methods getAllUsers (and the similar updateUserStatus) references a non-existent
MANAGER role causing only SYSTEM_ADMIN to have access; update the annotation to
use existing roles (e.g., replace 'MANAGER' with 'PRESIDENT', 'VICE_PRESIDENT',
or 'TEAM_LEADER' as appropriate) or add the MANAGER role to your security/roles
configuration so it is recognized—locate the annotations on the
AdminUserController methods (getAllUsers and updateUserStatus) and either change
the hasAnyRole(...) list to include real roles or ensure MANAGER is defined in
your Role/Authority setup to match the intended access model.

In
`@backend/src/main/java/org/sejongisc/backend/admin/repository/AdminUserRepository.java`:
- Around line 22-58: The LEFT JOIN in findAllByAdminFilter can emit duplicate
User rows when Account has multiple rows per (ownerId, type); either enforce
uniqueness on Account by adding a unique constraint for (ownerId, type) in the
Account entity (e.g., `@UniqueConstraint` on ownerId and type) or change the
repository query to deduplicate (use SELECT DISTINCT new
org.sejongisc.backend.admin.dto.AdminUserResponse(...) or wrap
aggregation/choose a single account per owner), and also add an ORDER BY (e.g.,
ORDER BY u.name or u.studentId) to the same JPQL to ensure deterministic result
ordering; update the AdminUserResponse/Account/AccountType references
accordingly.

In
`@backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserService.java`:
- Around line 116-119: deleteUser currently calls
userRepository.deleteById(userId) but related tables (Account, PointHistory,
UserBet, RefreshToken) reference users only by UUID fields and will leave
orphaned rows; either convert those entities to proper JPA relations with
`@ManyToOne/`@JoinColumn and cascade = CascadeType.ALL on User, or modify
deleteUser to explicitly remove dependent records before deleting the User—e.g.,
within the `@Transactional` deleteUser(UUID userId) call invoke repository deletes
such as accountRepository.deleteByOwnerId(userId),
pointHistoryRepository.deleteByUserId(userId),
userBetRepository.deleteByUserId(userId),
refreshTokenRepository.deleteByUserId(userId) (ensure corresponding repository
methods exist) then call userRepository.deleteById(userId).

In
`@backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserSyncService.java`:
- Around line 41-43: The current AdminUserSyncService code unconditionally
deactivates all users via userRepository.findAllByStatus(UserStatus.ACTIVE),
which risks disabling users not in the uploaded Excel and even SYSTEM_ADMIN;
change the flow to (1) parse the Excel first and build the set of user
identifiers or target cohort(s), (2) compute the precise set to deactivate as
active users that are NOT present in that parsed set (instead of all active
users), and (3) explicitly exclude any SYSTEM_ADMIN or other protected roles
when building that deactivation set; perform these operations inside the
existing transactional method (e.g., syncMemberData) so deactivation happens
only after successful parsing and syncing.

In `@backend/src/main/java/org/sejongisc/backend/user/entity/User.java`:
- Around line 52-54: The grade field in User (private Grade grade) is marked
nullable = false but lacks a builder or persistence-side default; add a safe
default either by annotating the field with Lombok's `@Builder.Default` (e.g.,
private `@Builder.Default` Grade grade = <appropriate default>) or by setting it
in the entity's `@PrePersist` method (ensure User#prePersist sets grade when
null), so User.builder() paths won't violate the DB constraint; update the User
class accordingly (keep createUserWithSignupAndPending unchanged).
🧹 Nitpick comments (8)
backend/src/main/java/org/sejongisc/backend/user/entity/Grade.java (1)

9-17: null 체크만 있고 isBlank() 체크가 누락되어 있습니다.

Gender.fromStringRole.fromPositionnull || isBlank()를 모두 확인하지만, Grade.fromStringnull만 확인합니다. 빈 문자열의 경우 결과적으로 NEW_MEMBER를 반환하므로 기능적 버그는 아니지만, 파싱 메서드 간 일관성을 맞추는 것이 좋습니다.

제안
   public static Grade fromString(String gradeStr) {
-    if (gradeStr == null) return NEW_MEMBER;
+    if (gradeStr == null || gradeStr.isBlank()) return NEW_MEMBER;
backend/src/main/java/org/sejongisc/backend/user/entity/Role.java (1)

14-32: AdminUserSyncService.mapRole()에서 Role.fromPosition() 호출로 통합

AdminUserSyncService.mapRole()Role.fromPosition()의 위치 매핑 로직이 완전히 동일합니다. AdminUserSyncServicemapRole() 메서드를 제거하고 Role.fromPosition()을 직접 호출하도록 변경하면 중복을 제거할 수 있습니다.

backend/src/main/java/org/sejongisc/backend/user/entity/Gender.java (1)

7-16: fromStringnull을 반환하는 점이 다른 enum 파서와 일관성이 없습니다.

Grade.fromStringNEW_MEMBER, Role.fromPositionTEAM_MEMBER를 기본값으로 반환하지만, Gender.fromString은 매칭 실패 또는 null 입력 시 null을 반환합니다. 현재 코드에서 applyExcelData는 null 가능한 Gender 값을 직접 할당하므로, 호출부에서 null 체크 없이 gender 필드의 메서드를 사용하면 NPE 위험이 있습니다. 성별이 선택적 필드라면 현재 설계가 타당하지만, 다른 enum 파서와의 일관성 차원에서 명시적인 문서화나 호출부의 null-safety 검증을 권장합니다.

backend/src/main/java/org/sejongisc/backend/common/auth/service/AuthService.java (1)

51-52: 토큰 길이 로깅은 디버그 레벨이 적합합니다.

토큰 길이 로깅은 운영 환경에서 매 로그인마다 INFO로 남을 필요는 없습니다. log.debug로 변경하는 것을 권장합니다.

♻️ 제안
-        log.info("created accessToken len={}", accessToken == null ? -1 : accessToken.length());
-        log.info("created refreshToken len={}", refreshToken == null ? -1 : refreshToken.length());
+        log.debug("created accessToken len={}", accessToken.length());
+        log.debug("created refreshToken len={}", refreshToken.length());
backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserSyncService.java (1)

111-136: 미사용 private 메서드 3개가 존재합니다.

mapGrade, mapRole, mapGender 메서드는 updateUserFromRow에서 호출되지 않습니다. 대신 Grade.fromString, Role.fromPosition, Gender.fromString 정적 메서드가 사용되고 있습니다. 데드 코드를 제거해 주세요.

♻️ 제안
-    /**
-     * 회원 등급(정/준회원) 매핑
-     */
-    private Grade mapGrade(String gradeStr) {
-        if (gradeStr.contains("정회원")) return Grade.REGULAR_MEMBER;
-        if (gradeStr.contains("준회원")) return Grade.ASSOCIATE_MEMBER;
-        return Grade.NEW_MEMBER;
-    }
-
-    /**
-     * 직위 텍스트를 통한 시스템 접근 권한(Role) 결정
-     */
-    private Role mapRole(String position) {
-        if (position == null || position.isBlank()) return Role.TEAM_MEMBER;
-        if (position.contains("회장") && !position.contains("부회장")) return Role.PRESIDENT;
-        if (position.contains("부회장") || position.contains("부대표자")) return Role.VICE_PRESIDENT;
-        if (position.contains("팀장")) return Role.TEAM_LEADER;
-        return Role.TEAM_MEMBER;
-    }
-
-    /**
-     * 성별 텍스트 변환
-     */
-    private Gender mapGender(String genderStr) {
-        if (genderStr == null || genderStr.isBlank()) return null;
-        if (genderStr.contains("남")) return Gender.MALE;
-        if (genderStr.contains("여")) return Gender.FEMALE;
-        return null;
-    }
backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserService.java (2)

31-31: DataFormatter는 thread-safe하지 않습니다.

AdminUserService는 싱글톤 빈이므로 DataFormatter 인스턴스가 여러 스레드에서 공유됩니다. 동시에 엑셀 업로드가 호출되면 데이터 손상이 발생할 수 있습니다. 메서드 로컬 변수로 변경하는 것을 권장합니다.

♻️ 제안
 public class AdminUserService {
     private final UserRepository userRepository;
     private final AdminUserRepository adminUserRepository;
     private final AdminUserSyncService adminUserSyncService;
-    private final DataFormatter formatter = new DataFormatter();

syncUsersFromExcel 메서드 내에서 로컬로 생성:

public ExcelSyncResponse syncUsersFromExcel(MultipartFile file) {
    DataFormatter formatter = new DataFormatter();
    // ...
}

getCellValue에 파라미터로 전달하거나, 메서드 내부에서 생성하도록 변경하세요.


144-168: 엑셀 컬럼 인덱스가 매직 넘버로 하드코딩되어 있습니다.

컬럼 인덱스(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)가 buildExcelRowsyncUsersFromExcel에 직접 사용되고 있어, 엑셀 포맷 변경 시 여러 곳을 수정해야 합니다. 상수로 추출하면 유지보수성이 향상됩니다.

♻️ 상수 추출 예시
private static final int COL_TEAM = 1;
private static final int COL_GENERATION = 2;
private static final int COL_NAME = 3;
private static final int COL_STUDENT_ID = 4;
private static final int COL_PHONE = 5;
private static final int COL_COLLEGE = 6;
private static final int COL_DEPARTMENT = 7;
private static final int COL_GRADE = 8;
private static final int COL_POSITION = 9;
private static final int COL_GENDER = 10;
backend/src/main/java/org/sejongisc/backend/admin/controller/AdminUserController.java (1)

63-68: 응답 타입이 일관되지 않습니다.

updateUserStatus(Line 63)와 updateUserRole(Line 75), forceDeleteUser(Line 93)은 ResponseEntity<?>를 사용하지만, promoteToSenior(Line 85)는 ResponseEntity<Void>를 사용합니다. 모두 noContent()를 반환하므로 ResponseEntity<Void>로 통일하는 것이 좋습니다.

♻️ 제안
-    public ResponseEntity<?> updateUserStatus(
+    public ResponseEntity<Void> updateUserStatus(
...
-    public ResponseEntity<?> updateUserRole(
+    public ResponseEntity<Void> updateUserRole(
...
-    public ResponseEntity<?> forceDeleteUser(`@PathVariable` UUID userId) {
+    public ResponseEntity<Void> forceDeleteUser(`@PathVariable` UUID userId) {

Also applies to: 75-80, 85-88, 93-96

Comment on lines +54 to 58
@PreAuthorize("hasAnyRole('SYSTEM_ADMIN', 'MANAGER')") // TODO: 현재 MANAGER role은 존재하지 않음. 변경 필요
public ResponseEntity<List<AdminUserResponse>> getAllUsers(@ModelAttribute AdminUserRequest request) {
// TODO: 페이징 추후 고려
return ResponseEntity.ok(adminUserService.findAllUsers(request));
}
Copy link

@coderabbitai coderabbitai bot Feb 13, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

존재하지 않는 MANAGER 역할이 @PreAuthorize에 사용되고 있습니다.

TODO에도 언급되어 있지만, MANAGER 역할이 존재하지 않으면 실질적으로 SYSTEM_ADMIN만 접근 가능합니다. Line 62의 updateUserStatus에도 동일한 문제가 있습니다. 의도된 접근 권한을 명확히 하고, 존재하는 역할(예: PRESIDENT, VICE_PRESIDENT, TEAM_LEADER)로 교체하거나 MANAGER 역할을 추가해야 합니다.

이 이슈를 추적하기 위한 새 이슈를 생성할까요?

🤖 Prompt for AI Agents
In
`@backend/src/main/java/org/sejongisc/backend/admin/controller/AdminUserController.java`
around lines 54 - 58, The `@PreAuthorize` annotation in AdminUserController on
methods getAllUsers (and the similar updateUserStatus) references a non-existent
MANAGER role causing only SYSTEM_ADMIN to have access; update the annotation to
use existing roles (e.g., replace 'MANAGER' with 'PRESIDENT', 'VICE_PRESIDENT',
or 'TEAM_LEADER' as appropriate) or add the MANAGER role to your security/roles
configuration so it is recognized—locate the annotations on the
AdminUserController methods (getAllUsers and updateUserStatus) and either change
the hasAnyRole(...) list to include real roles or ensure MANAGER is defined in
your Role/Authority setup to match the intended access model.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@discipline24
TODO에도 언급했지만 roleMANAGER가 없어서 확인 필요합니당
SessionRoleMANAGER를 의도한 거였을까요??

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
`@backend/src/main/java/org/sejongisc/backend/admin/controller/AdminUserController.java`:
- Around line 93-99: The Swagger description in AdminUserController's `@Operation`
for forceDeleteUser is incorrect — it claims a permanent delete while the
service performs a soft delete (adminUserService.deleteUser /
deleteUserSoftDelete). Update the `@Operation` description to state that this
endpoint performs a soft delete (or uses the service's soft-delete behavior),
ensuring the docs match the actual behavior; edit the description string on the
`@Operation` annotation for the forceDeleteUser method accordingly.

In
`@backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserService.java`:
- Line 31: The DataFormatter instance is not thread-safe and must not be a
singleton field in AdminUserService; change the usage so that DataFormatter is
created locally inside syncUsersFromExcel (or created per call) and pass that
local DataFormatter into getCellValue instead of using the private final field;
update references to DataFormatter (remove the field declaration) and modify
syncUsersFromExcel and getCellValue signatures/uses to accept a DataFormatter
parameter or instantiate one inside getCellValue so each thread gets its own
instance.

In `@backend/src/main/java/org/sejongisc/backend/user/entity/User.java`:
- Around line 106-109: canLogin() currently only blocks UserStatus.OUT (return
this.status != UserStatus.OUT) but the USER_NOT_ACTIVE error message implies all
non-ACTIVE users are blocked; update the error message to reflect that only
withdrawn users cannot log in (e.g., "탈퇴한 회원은 로그인할 수 없습니다") and replace
occurrences where USER_NOT_ACTIVE is thrown/returned so the text matches the
canLogin() logic (search for usages of canLogin() and the USER_NOT_ACTIVE
constant or enum to update the message).
- Around line 131-154: applyExcelData currently accepts nullable inputs from
AdminUserSyncService.updateUserFromRow and writes them directly to DB fields
(notably name annotated `@Column`(nullable = false) and gender which can be null
from Gender.fromString), risking constraint violations or unintended nulls;
update applyExcelData (or its caller) to validate and sanitize inputs before
assignment: ensure name is non-null/non-empty (throw/return error or substitute
a safe default) and map/validate gender (use a safe fallback or throw when
Gender.fromString returns null), while keeping Grade.fromString and
Role.fromPosition behavior unchanged; reference applyExcelData,
AdminUserSyncService.updateUserFromRow, and Gender.fromString when making the
fix.
🧹 Nitpick comments (4)
backend/src/main/java/org/sejongisc/backend/user/service/UserService.java (1)

208-215: deleteUserSoftDelete에서도 findUser 헬퍼 사용 권장.

기존 deleteUserSoftDelete 메서드도 동일한 findById + orElseThrow 패턴을 사용하고 있으므로, 새로 추가된 findUser 헬퍼로 통일하면 일관성이 향상됩니다.

♻️ 리팩토링 제안
     `@Transactional`
     public void deleteUserSoftDelete(UUID userId) {
-        User user = userRepository.findById(userId)
-            .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
+        User user = findUser(userId);
         user.setStatus(UserStatus.OUT);
         refreshTokenService.deleteByUserId(userId);
         log.info("회원 softdelete 처리 완료: userId={}", userId);
     }
backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserService.java (2)

137-161: 엑셀 컬럼 인덱스에 매직 넘버 대신 상수를 사용하세요.

getCellValue(row, 4), getCellValue(row, 3) 등 여러 곳에서 하드코딩된 컬럼 인덱스를 사용하고 있어, 엑셀 양식이 변경되면 수정 누락이 발생하기 쉽습니다.

♻️ 상수 정의 예시
+    private static final int COL_TEAM = 1;
+    private static final int COL_GENERATION = 2;
+    private static final int COL_NAME = 3;
+    private static final int COL_STUDENT_ID = 4;
+    private static final int COL_PHONE = 5;
+    private static final int COL_COLLEGE = 6;
+    private static final int COL_DEPARTMENT = 7;
+    private static final int COL_GRADE = 8;
+    private static final int COL_POSITION = 9;
+    private static final int COL_GENDER = 10;

36-69: 엑셀 파싱 시 Zip Bomb(압축 폭탄) 방어를 명시적으로 설정하세요.

XSSFWorkbook은 ZIP 기반 .xlsx 파일을 처리하므로, 악의적인 사용자가 극도로 압축된 엑셀 파일을 업로드하여 서버 메모리를 소진시킬 수 있습니다. 현재 validateFile() 메서드는 파일 크기 검증을 하지 않으며, application.yml에 Spring의 MultipartFile 최대 크기 제한이 명시적으로 설정되어 있지 않습니다. 다음을 추가하기를 권장합니다:

  1. application.yml에서 spring.servlet.multipart.max-file-sizemax-request-size 명시적 설정
  2. validateFile() 메서드에 파일 크기 검증 로직 추가
  3. 행 수 상한선 설정 (예: if (excelRows.size() > MAX_ALLOWED_ROWS))
backend/src/main/java/org/sejongisc/backend/admin/controller/AdminUserController.java (1)

66-71: ResponseEntity<?> 와일드카드 타입을 ResponseEntity<Void>로 통일하세요.

promoteToSenior(Line 88)은 ResponseEntity<Void>를 반환하는 반면, updateUserStatusupdateUserRoleResponseEntity<?>를 사용합니다. 본문이 없는 204 응답은 모두 ResponseEntity<Void>로 통일하는 것이 Swagger 문서 생성 및 타입 안전성 측면에서 좋습니다.

Also applies to: 78-83

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserService.java`:
- Around line 128-133: The file extension check in validateFile is
case-sensitive; update validateFile in AdminUserService to perform a
case-insensitive check (e.g., getOriginalFilename(), trim it, convert to
lowercase with Locale.ROOT and then call endsWith(".xlsx")) so .XLSX/.Xlsx
variants are accepted, and continue to throw
CustomException(ErrorCode.INVALID_FILE_FORMAT) when the normalized name doesn't
end with ".xlsx".

In
`@backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserSyncService.java`:
- Around line 64-70: The signup reward block (Account userAccount =
accountService.createUserAccount(...);
pointLedgerService.processTransaction(...)) can throw and cause the surrounding
transaction in AdminUserSyncService to roll back; extract the reward logic into
its own transactional boundary or guard it with a try-catch so user updates are
not partially rolled back. Concretely, move the createUserAccount +
processTransaction calls into a new method (e.g., issueSignupReward) annotated
with `@Transactional`(propagation = REQUIRES_NEW) or call them inside a try { ...
} catch(Exception e) { log error with savedUser.getUserId(),
TransactionReason.SIGNUP_REWARD, and AccountEntry references } so failures are
logged/handled and do not abort the main sync loop.
- Around line 52-55: Replace the insecure use of rowData.phone() as the initial
password in AdminUserSyncService where existingUser.orElseGet builds a User with
passwordHash set via passwordEncoder.encode(rowData.phone()); instead generate a
secure random temporary password (or create a one-time email/password reset
token) and store its encoded value in passwordHash, and set a flag on the User
(e.g., requirePasswordChange or needsInitialSetup) so first-login forces a reset
or send a password-setup link to the user; update the user creation logic in the
User.builder() branch and any related authentication/first-login flow to respect
the requirePasswordChange flag.
🧹 Nitpick comments (2)
backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserSyncService.java (1)

107-110: parseGeneration 방어 코딩

현재 호출 경로상 genStrnull이 될 가능성은 낮지만, null 입력 시 NullPointerException이 발생합니다. 방어적으로 null 체크를 추가하면 안전합니다.

♻️ 개선 제안
 private int parseGeneration(String genStr) {
+    if (genStr == null || genStr.isBlank()) return 0;
     String clean = genStr.replaceAll("[^0-9]", "");
     return clean.isEmpty() ? 0 : Integer.parseInt(clean);
 }
backend/src/main/java/org/sejongisc/backend/admin/service/AdminUserService.java (1)

138-161: 엑셀 컬럼 인덱스가 매직 넘버로 사용되고 있습니다.

getCellValue(row, 4, formatter), getCellValue(row, 3, formatter) 등 하드코딩된 인덱스는 엑셀 구조가 변경될 때 유지보수가 어렵습니다. 상수나 enum으로 추출하면 가독성과 유지보수성이 개선됩니다.

♻️ 예시
private static final int COL_TEAM = 1;
private static final int COL_GENERATION = 2;
private static final int COL_NAME = 3;
private static final int COL_STUDENT_ID = 4;
private static final int COL_PHONE = 5;
private static final int COL_COLLEGE = 6;
private static final int COL_DEPARTMENT = 7;
private static final int COL_GRADE = 8;
private static final int COL_POSITION = 9;
private static final int COL_GENDER = 10;

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.

⚙️ [기능추가][관리자] 관리자의 회원 관리 기능 추가

1 participant