Skip to content

[WTH-468] 백엔드 패널티 관리 UI 변경에 따른 수정사항 반영 - #97

Open
woneeeee wants to merge 22 commits into
devfrom
fix/WTH-468-백엔드-패널티-관리-UI-변경에-따른-수정사항-반영

Hidden character warning

The head ref may contain hidden characters: "fix/WTH-468-\ubc31\uc5d4\ub4dc-\ud328\ub110\ud2f0-\uad00\ub9ac-UI-\ubcc0\uacbd\uc5d0-\ub530\ub978-\uc218\uc815\uc0ac\ud56d-\ubc18\uc601"
Open

[WTH-468] 백엔드 패널티 관리 UI 변경에 따른 수정사항 반영#97
woneeeee wants to merge 22 commits into
devfrom
fix/WTH-468-백엔드-패널티-관리-UI-변경에-따른-수정사항-반영

Conversation

@woneeeee

@woneeeee woneeeee commented Aug 24, 2026

Copy link
Copy Markdown
Member

📌 Summary

어떤 작업인지 한 줄 요약해 주세요.

페널티 관리 UI 개편에 맞춰 페널티 관련 API를 전반적으로 수정하고, 마이페이지 및 어드민 페이지에 페널티 관련 기능을 추가합니다.

📝 Changes

1. 페널티 부여 수정 POST /api/v4/admin/clubs/{clubId}/penalties

  • 단일 멤버 → 다수 멤버 동시 부여 (userIds: List<Long>)
  • 점수제 도입 (score: Int, 기본값 1) — 한 번에 여러 점수 부여 가능
  • 페널티 사유 필수 (penaltyDescription: @NotBlank)

2. 페널티 수정 추가 PUT /api/v4/admin/clubs/{clubId}/penalties/{penaltyId}

  • 페널티 사유 + 점수 수정 가능
  • 점수 변경 시 ClubMember.penaltyCount / warningCount 자동 delta 보정

3. 페널티 삭제 수정 DELETE /api/v4/admin/clubs/{clubId}/penalties/{penaltyId}

  • 기존 카운트 1 차감 → 삭제된 페널티의 score만큼 차감

4. 어드민 멤버 페널티 상세 조회 신규 GET /api/v4/admin/clubs/{clubId}/penalties/members/{clubMemberId}

  • 응답: 프로필 이미지, 이름, 활동 기수 목록, 활동 상태, 자기소개, 페널티 목록 (사유/점수/날짜)

5. 페널티 규정 저장 신규 PUT /api/v4/admin/clubs/{clubId}/penalties/rule

  • 어드민이 동아리별 페널티 규정 텍스트 저장 (최대 500자, null 허용)

6. 어드민 멤버 목록에 페널티 정보 추가 GET /api/v4/admin/clubs/{clubId}/members

  • 응답에 penaltyCount, lastPenaltyAt 추가

7. 마이페이지 stats에 페널티 갯수 추가 GET /api/v4/clubs/{clubId}/users/me/mypage

  • stats.penaltyCount 추가

8. 마이페이지 페널티 목록 조회 신규 GET /api/v4/clubs/{clubId}/users/me/mypage/penalties

  • 무한스크롤 (SliceResponse)
  • 응답: 페널티 ID, 점수, 사유, 타입, 생성일

9. 마이페이지 페널티 규정 조회 신규 GET /api/v4/clubs/{clubId}/users/me/mypage/penalty-rule

  • 해당 동아리의 페널티 규정 텍스트 반환

10. 한국어 표기 일괄 수정

  • "패널티" → "페널티" (응답 메시지, 주석 등)

11. 어드민 멤버 검색 조회 신규 GET /api/v4/admin/clubs/{clubId}/members/search

  • 멤버 이름으로 검색 (keyword 필수)
  • 전체 또는 특정 기수(cardinalNumber) 필터링
  • 페이지네이션 지원 (page, size)
  • 응답: 기존 멤버 목록 조회와 동일한 ClubMemberResponse 반환

페널티 / 경고 분리 현황

현재 PenaltyType enum으로 PENALTY / WARNING 두 타입이 구분되어 있으며, ClubMemberpenaltyCountwarningCount가 별도로 관리됩니다.

현재 상태: Club.warningEnabled의 기본값이 false이므로, 프론트에서 penaltyType: "WARNING"을 전송하면 WarningNotEnabledException이 발생합니다. 경고 타입은 현재 실제로 사용할 수 없습니다.

경고 기능 활성화를 위해 필요한 것:

  • 동아리별 경고 기능 ON/OFF API 구현 (PUT /api/v4/admin/clubs/{clubId}/warning-enabled 예정)
  • 해당 API로 warningEnabled = true 설정 후 프론트에서 penaltyType: "WARNING" 전송 시 warningCount 증가
  • 경고 누적 시 자동 페널티 전환 규칙이 필요하다면 별도 도메인 로직 추가 필요

테스트 항목

  • UpdatePenaltyUseCase (15개): 페널티 수정, 카운트 조정, 권한 검증
  • GetPenaltyQueryService (8개): 기수별/멤버별 조회, 권한 검증
  • SavePenaltyRuleUseCase (4개): 규정 저장/초기화
  • GetPenaltyRuleQueryService (3개): 규정 조회
  • Penalty Entity (8개): 상태 전환, 유효성 검증, 비즈니스 로직
  • GetClubMemberQueryService (4개): 이름 검색, 기수별 필터링, 결과 없음 처리, 정렬
  • ClubAdminController (4개): 응답 코드, 기수 검색, 검색 결과 없음, 페이지네이션

📸 Screenshots / Logs

필요시 스크린샷 or 로그를 첨부해주세요.

image image image

💡 Reviewer 참고사항

리뷰에 참고할 내용을 작성해주세요.

슬랙에도 적어뒀는데 페널티에서 멤버 검색을 할때 해당 기수 전체 멤버 검색 api가 필요할 것 같습니다! 페널티 도메인보다 어드민 멤버 목록 API에 검색 파라미터 추가하거나 따로 공통 검색 API를 분리하는게 나을 것 같은데 어떻게 생각하시나욤...?? 의견 주시면 해당 피알이나 다음 피알에서 함께 반영해보겠습니닷...!! -> 구현 완료

✅ Checklist

  • PR 제목 설정 완료 (WTH-123 인증 필터 설정)
  • 테스트 구현 완료
  • 리뷰어 등록 완료
  • 자체 코드 리뷰 완료

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 50c000ef-a9b4-44ff-a04e-c1cb91a8638e


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.

@woneeeee woneeeee changed the title Fix/wth 468 백엔드 패널티 관리 UI 변경에 따른 수정사항 반영 [WTH-468] 백엔드 패널티 관리 UI 변경에 따른 수정사항 반영 Aug 24, 2026
@woneeeee woneeeee self-assigned this Aug 24, 2026
@woneeeee woneeeee added the 📬 API 서버 API 통신 label Aug 24, 2026
@woneeeee
woneeeee requested review from hyxklee and soo0711 August 24, 2026 08:15

@hyxklee hyxklee left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

우선 어드민쪽 작업 먼저 리뷰를 했습니당
관련된 리뷰를 보시고, 일단 이번 PR에서 유저쪽은 다음 이슈로 분리해서 진행하는게 좋을 것 같아용

어드민 쪽에 달린 PR이 유저 쪽에서도 해당하는 경우가 많을테니 함께 수정하면서 작업을 해주시면 될 것 같습니당

고생하셨어요! 처음인데 크게 벗어나는 것 없이 작업이 되었네용

var backgroundImageStorageKey: String? = backgroundImageStorageKey
private set

@Column(nullable = false)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

DB 변경시 resources/db/migration에 마이그레이션 쿼리가 필요합니당
Flyway에 대해서 한 번 Claude와 함께 이야기해보고, 이해한 후에 추가해주세용

var warningEnabled: Boolean = false
private set

@Column(length = 500)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nullable 옵션도 넣어주면 조을 것 같아용

@RequestParam(required = false) cardinalNumber: Int?,
@RequestParam(defaultValue = "0") page: Int,
@RequestParam(defaultValue = "20") size: Int,
): CommonResponse<PageResponse<ClubMemberResponse>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

검색의 경우는 Pagination이 굳이 없어도 될 것 같아요!
추가하신 별도의 의도가 있을까요??

val score: Int = 1,
@field:Schema(description = "페널티 사유", example = "정기모임 무단 불참")
@field:NotBlank
val penaltyDescription: String,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

사유가 디자인 스펙에는 몇 자인지 안 나타나있는 것 같네용 한 번 확인해서 반영해주세요!


val penalty = mapper.toEntity(request, clubMember, cardinal)
penaltyRepository.save(penalty)
request.userIds.forEach { targetUserId ->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

이렇게 내부에서 forEach를 통해 db에서 조회를 하게 되면 request.userIds 만큼 쿼리가 날라갑니당
해당 유스케이스의 경우에는 많은 인원을 선택해서 들어오는 경우는 적을 것 같긴 하지만, 이런 점을 인지하고 BE 개발을 하시면 좋습니당

BE는 항상 DB와 엮여있기 때문이에오

clubMemberRepository.findByIdWithLock(clubMember.id)
?: throw PenaltyNotFoundException()
lockedMember.incrementPenaltyCount()
if (request.penaltyType == PenaltyType.WARNING && !clubMember.club.warningEnabled) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

반복문 내부에서 경고 활성화 여부를 확인하고 있는 것 같네용
이렇게 되면 clubMember.club을 가져오면서 club을 조회하는 SELECT 문이 반복해서 나가게될 것 같아요 (FetchJoin을 이용하면 clubMember를 조회할 때 club도 같이 조회할 수 있습니다. 이건 FetchJoin, N+1 문제를 찾아보시면 좋아요)

디자인 스펙을 보면 경고/패널티는 한 번의 요청에서 섞이지 않는 것으로 나옵니당 WARGING인 경우는 반복문 밖 (위)에서 처리해주는게 좋아보여요

class GetPenaltyRuleQueryService(
private val clubReader: ClubReader,
) {
@Transactional(readOnly = true)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

쿼리 서비스에서는 클래스에 @transactional(readOnly = true)를 다는 것이 컨벤션입니당
클로드가 못잡아줬나보네요..

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

하네스 업데이트를 해야하나

import org.springframework.web.bind.annotation.RestController

@Tag(name = "PENALTY", description = "패널티 API")
@Tag(name = "PENALTY", description = "페널티 API")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

이번 작업은 어드민쪽 작업까지로 분리하고, 유저쪽은 PR을 나눠서 작업하면 좋을 것 같아용
BE는 FE에 비해 비교적 코드 양이 적기 때문에 500줄 정도면 큰 편으로 생각합니당

베스트는 300줄 이내, 큰 작업의 경우는 500줄 정도로 PR 볼륨을 잡고 작업을 분리하면서 하시면 좋을 것 같아요!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📬 API 서버 API 통신

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants