-
Notifications
You must be signed in to change notification settings - Fork 8
feat: 어드민 유저 관리 기능 추가 #686
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package com.example.solidconnection.admin.controller; | ||
|
|
||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.ModelAttribute; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import com.example.solidconnection.admin.dto.RestrictedUserInfoDetailResponse; | ||
| import com.example.solidconnection.admin.dto.RestrictedUserSearchCondition; | ||
| import com.example.solidconnection.admin.dto.RestrictedUserSearchResponse; | ||
| import com.example.solidconnection.admin.dto.UserInfoDetailResponse; | ||
| import com.example.solidconnection.admin.dto.UserSearchCondition; | ||
| import com.example.solidconnection.admin.dto.UserSearchResponse; | ||
| import com.example.solidconnection.admin.service.AdminUserService; | ||
| import com.example.solidconnection.common.response.PageResponse; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @RequestMapping("/admin/users") | ||
| @RestController | ||
| public class AdminUserController { | ||
|
|
||
| private final AdminUserService adminUserService; | ||
|
|
||
| @GetMapping | ||
| public ResponseEntity<PageResponse<UserSearchResponse>> searchAllUsers( | ||
| @Valid @ModelAttribute UserSearchCondition searchCondition, | ||
| Pageable pageable | ||
| ) { | ||
| Page<UserSearchResponse> page = adminUserService.searchAllUsers(searchCondition, pageable); | ||
| return ResponseEntity.ok(PageResponse.of(page)); | ||
| } | ||
|
|
||
| @GetMapping("/{user-id}") | ||
| public ResponseEntity<UserInfoDetailResponse> getUserInfoDetail( | ||
| @PathVariable(name = "user-id") long userId | ||
| ) { | ||
| UserInfoDetailResponse response = adminUserService.getUserInfoDetail(userId); | ||
| return ResponseEntity.ok(response); | ||
| } | ||
|
|
||
| @GetMapping("/restricted") | ||
| public ResponseEntity<PageResponse<RestrictedUserSearchResponse>> searchRestrictedUsers( | ||
| @Valid @ModelAttribute RestrictedUserSearchCondition searchCondition, | ||
| Pageable pageable | ||
| ) { | ||
| Page<RestrictedUserSearchResponse> page = adminUserService.searchRestrictedUsers(searchCondition, pageable); | ||
| return ResponseEntity.ok(PageResponse.of(page)); | ||
| } | ||
|
|
||
| @GetMapping("/restricted/{user-id}") | ||
| public ResponseEntity<RestrictedUserInfoDetailResponse> getRestrictedUserInfoDetail( | ||
| @PathVariable(name = "user-id") long userId | ||
| ) { | ||
| RestrictedUserInfoDetailResponse response = adminUserService.getRestrictedUserInfoDetail(userId); | ||
| return ResponseEntity.ok(response); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import java.time.ZonedDateTime; | ||
|
|
||
| public record BannedHistoryResponse( | ||
| ZonedDateTime createdAt | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import com.example.solidconnection.siteuser.domain.UserBanDuration; | ||
|
|
||
| public record BannedInfoResponse( | ||
| boolean isBanned, | ||
| UserBanDuration duration | ||
| ) { | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import java.time.ZonedDateTime; | ||
|
|
||
| public record MatchedInfoResponse( | ||
| String nickname, | ||
| ZonedDateTime matchedDate | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record MenteeInfoResponse( | ||
| UnivApplyInfoResponse univApplyInfos, | ||
| List<MatchedInfoResponse> mentorInfos | ||
| ) { | ||
|
Comment on lines
+5
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find and examine UnivApplyInfoResponse.java
fd "UnivApplyInfoResponse.java" --type f --exec cat -n {} \;Repository: solid-connection/solid-connect-server Length of output: 2255 필드명과 타입의 단수/복수 네이밍을 통일해 주세요.
🤖 Prompt for AI Agents |
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import com.example.solidconnection.mentor.domain.MentorApplicationStatus; | ||
| import java.time.ZonedDateTime; | ||
|
|
||
| public record MentorApplicationHistoryInfoResponse( | ||
| MentorApplicationStatus mentorApplicationStatus, | ||
| String rejectedReason, | ||
| ZonedDateTime createdAt | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record MentorInfoResponse( | ||
| List<MatchedInfoResponse> menteeInfos, | ||
| List<MentorApplicationHistoryInfoResponse> mentorApplicationHistory | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import java.time.ZonedDateTime; | ||
|
|
||
| import com.example.solidconnection.report.domain.ReportType; | ||
|
|
||
| public record ReportedHistoryResponse( | ||
| ZonedDateTime reportedDate, | ||
| ReportType reportType | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import java.time.ZonedDateTime; | ||
|
|
||
| import com.example.solidconnection.report.domain.ReportType; | ||
| import com.example.solidconnection.report.domain.TargetType; | ||
|
|
||
| public record ReportedInfoResponse( | ||
| ZonedDateTime reportedDate, | ||
| TargetType targetType, | ||
| ReportType reportType | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record RestrictedUserInfoDetailResponse( | ||
| List<ReportedHistoryResponse> reportedHistoryResponses, | ||
| List<BannedHistoryResponse> bannedHistoryResponses | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import com.example.solidconnection.siteuser.domain.Role; | ||
| import com.example.solidconnection.siteuser.domain.UserStatus; | ||
|
|
||
| public record RestrictedUserSearchCondition( | ||
| Role role, | ||
| UserStatus userStatus, | ||
| String keyword | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import com.example.solidconnection.siteuser.domain.Role; | ||
| import com.example.solidconnection.siteuser.domain.UserStatus; | ||
|
|
||
| public record RestrictedUserSearchResponse( | ||
| String nickname, | ||
| Role role, | ||
| UserStatus userStatus, | ||
| ReportedInfoResponse reportedInfoResponse, | ||
| BannedInfoResponse bannedInfoResponse | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| public record UnivApplyInfoResponse( | ||
| String firstChoiceUnivName, | ||
| String secondChoiceUnivName, | ||
| String thirdChoiceUnivName | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record UserInfoDetailResponse( | ||
| MentorInfoResponse mentorInfoResponse, // 멘티일 경우 null | ||
| MenteeInfoResponse menteeInfoResponse, // 멘토일 경우 null | ||
| List<ReportedHistoryResponse> reportedHistoryResponses | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import com.example.solidconnection.siteuser.domain.Role; | ||
|
|
||
| public record UserSearchCondition( | ||
| Role role, | ||
| String keyword | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.example.solidconnection.admin.dto; | ||
|
|
||
| import com.example.solidconnection.siteuser.domain.Role; | ||
| import com.example.solidconnection.siteuser.domain.UserStatus; | ||
|
|
||
| public record UserSearchResponse( | ||
| String nickname, | ||
| String email, | ||
| Role role, | ||
| UserStatus userStatus | ||
| ) { | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||
| package com.example.solidconnection.admin.service; | ||||||
|
|
||||||
| import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; | ||||||
|
|
||||||
| import org.springframework.data.domain.Page; | ||||||
| import org.springframework.data.domain.Pageable; | ||||||
| import org.springframework.stereotype.Service; | ||||||
| import org.springframework.transaction.annotation.Transactional; | ||||||
| import com.example.solidconnection.admin.dto.RestrictedUserInfoDetailResponse; | ||||||
| import com.example.solidconnection.admin.dto.RestrictedUserSearchCondition; | ||||||
| import com.example.solidconnection.admin.dto.RestrictedUserSearchResponse; | ||||||
| import com.example.solidconnection.admin.dto.UserInfoDetailResponse; | ||||||
| import com.example.solidconnection.admin.dto.UserSearchCondition; | ||||||
| import com.example.solidconnection.admin.dto.UserSearchResponse; | ||||||
| import com.example.solidconnection.common.exception.CustomException; | ||||||
| import com.example.solidconnection.siteuser.domain.SiteUser; | ||||||
| import com.example.solidconnection.siteuser.repository.SiteUserRepository; | ||||||
| import com.example.solidconnection.siteuser.repository.custom.SiteUserFilterRepository; | ||||||
| import lombok.RequiredArgsConstructor; | ||||||
|
|
||||||
| @RequiredArgsConstructor | ||||||
| @Service | ||||||
| public class AdminUserService { | ||||||
|
|
||||||
| private final SiteUserRepository siteUserRepository; | ||||||
| private final SiteUserFilterRepository siteUserFilterRepository; | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 커스템 레포지토리를 구현하는 경우 JPA 레포지토리에 추가로 이런 느낌으로 작성해주시면 좋을 거 같습니다
Suggested change
|
||||||
|
|
||||||
| @Transactional(readOnly = true) | ||||||
| public Page<UserSearchResponse> searchAllUsers(UserSearchCondition searchCondition, Pageable pageable) { | ||||||
| return siteUserFilterRepository.searchAllUsers(searchCondition, pageable); | ||||||
| } | ||||||
|
|
||||||
| @Transactional(readOnly = true) | ||||||
| public Page<RestrictedUserSearchResponse> searchRestrictedUsers(RestrictedUserSearchCondition searchCondition, Pageable pageable) { | ||||||
| return siteUserFilterRepository.searchRestrictedUsers(searchCondition, pageable); | ||||||
| } | ||||||
|
|
||||||
| @Transactional(readOnly = true) | ||||||
| public UserInfoDetailResponse getUserInfoDetail(long userId) { | ||||||
| SiteUser siteUser = siteUserRepository.findById(userId) | ||||||
| .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); | ||||||
| return siteUserFilterRepository.getUserInfoDetailByUserId(siteUser.getId()); | ||||||
| } | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Override
public UserInfoDetailResponse getUserInfoDetailByUserId(long userId) {
SiteUser user = queryFactory
.selectFrom(siteUser)
.where(siteUser.id.eq(userId))
.fetchOne();
// ...
}
|
||||||
|
|
||||||
| @Transactional(readOnly = true) | ||||||
| public RestrictedUserInfoDetailResponse getRestrictedUserInfoDetail(long userId) { | ||||||
| SiteUser siteUser = siteUserRepository.findById(userId) | ||||||
| .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); | ||||||
| return siteUserFilterRepository.getRestrictedUserInfoDetail(siteUser.getId()); | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.example.solidconnection.siteuser.repository.custom; | ||
|
|
||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.Pageable; | ||
| import com.example.solidconnection.admin.dto.RestrictedUserInfoDetailResponse; | ||
| import com.example.solidconnection.admin.dto.RestrictedUserSearchCondition; | ||
| import com.example.solidconnection.admin.dto.RestrictedUserSearchResponse; | ||
| import com.example.solidconnection.admin.dto.UserInfoDetailResponse; | ||
| import com.example.solidconnection.admin.dto.UserSearchCondition; | ||
| import com.example.solidconnection.admin.dto.UserSearchResponse; | ||
|
|
||
| public interface SiteUserFilterRepository { | ||
|
|
||
| Page<UserSearchResponse> searchAllUsers(UserSearchCondition searchCondition, Pageable pageable); | ||
|
|
||
| Page<RestrictedUserSearchResponse> searchRestrictedUsers(RestrictedUserSearchCondition searchCondition, Pageable pageable); | ||
|
|
||
| UserInfoDetailResponse getUserInfoDetailByUserId(long userId); | ||
|
|
||
| RestrictedUserInfoDetailResponse getRestrictedUserInfoDetail(long userId); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 95
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 63
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 158
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 3009
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 4166
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 2253
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 63
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 353
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 535
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 65
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 1253
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 3198
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 823
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 6151
🌐 Web query:
Jackson 2.15 Java record boolean is prefix serialization property naming💡 Result:
Jackson (including 2.15) derives JSON property names using JavaBean rules. For a boolean accessor named
isXxx(), the logical property name becomesxxx(theisprefix is stripped), so a record component likeboolean isActivetypically serializes as"active": true, not"isActive": true. [1]How to keep the
isin the JSON name (recommended): annotate the record component@JsonPropertyexplicitly sets the external (JSON) name. [2][3]Alternative approaches
activeand (if you really need the JSON key to beisActive) expose it with@JsonProperty("isActive")on the component. [2][3]is*names for booleans. [1]Sources
[1] javathinking.com — explanation of Jackson stripping
isfor primitive boolean properties and options to prevent it[2] Jackson Annotations wiki (FasterXML) —
@JsonPropertypurpose (define external/property name)[3]
@JsonPropertyJavadoc — defines the logical/external property name used in JSON🌐 Web query:
Spring Boot 3.1.5 Jackson version record support is prefix handling💡 Result:
Spring Boot 3.1.5 ships with Jackson 2.15.3 (via dependency management / Jackson BOM). [1]
Records are supported by Jackson (databind) and Jackson’s record introspection also recognizes regular getters and
is*getters. That means a record component likeboolean isActivegenerates an accessorisActive()which Jackson can treat as an “is-getter” and apply standard bean name mangling (strip theis), producing JSON like{"active": true}instead of{"isActive": true}. [2][3][4]How to keep the
isprefix (common options):isprefix (e.g.,active).AUTO_DETECT_IS_GETTERSfor this. [3]Sources:
[1] Spring Boot 3.1.5 dependency versions (Jackson 2.15.3) (docs.spring.io)
[2] Jackson
DefaultAccessorNamingStrategy.RecordNaming(records also recognizeis-getters) (javadoc.io)[3] Jackson
MapperFeature.AUTO_DETECT_IS_GETTERS(fasterxml.github.io)[4] Jackson default naming removes
get/set/isprefixes (javadoc.io)Citations:
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 63
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 3218
🏁 Script executed:
Repository: solid-connection/solid-connect-server
Length of output: 2601
isBanned컴포넌트가 Jackson 직렬화 시"banned"으로 출력됩니다.Spring Boot 3.1.5의 Jackson 2.15.3는 JavaBean 명명 규칙을 따르며, 불린 타입 접근자(getter)에서
is접두사를 제거합니다. 따라서isBanned()메서드는 JSON에서"banned"로 직렬화됩니다. 프론트엔드가"isBanned"를 기대한다면 API 계약이 깨집니다.다음 중 하나를 선택하여 수정하세요:
컴포넌트명 변경 (명시적 이름 변경)
@JsonProperty사용 (권장 - 기존 코드베이스 패턴과 일치)🤖 Prompt for AI Agents