Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import inha.gdgoc.domain.auth.service.MailService;
import inha.gdgoc.domain.auth.service.RefreshTokenService;
import inha.gdgoc.domain.user.entity.User;
import inha.gdgoc.domain.user.enums.TeamType;
import inha.gdgoc.domain.user.enums.UserRole;
import inha.gdgoc.domain.user.repository.UserRepository;
import inha.gdgoc.global.config.jwt.TokenProvider;
Expand Down Expand Up @@ -165,15 +166,30 @@ public ResponseEntity<ApiResponse<Void, Void>> resetPassword(@RequestBody Passwo
* 예) /api/v1/auth/LEAD, /api/v1/auth/ORGANIZER, /api/v1/auth/ADMIN
*/
@GetMapping("/{role}")
public ResponseEntity<ApiResponse<Void, ?>> checkRole(@AuthenticationPrincipal TokenProvider.CustomUserDetails me, @PathVariable UserRole role) {
public ResponseEntity<ApiResponse<Void, ?>> checkRoleOrTeam(@AuthenticationPrincipal TokenProvider.CustomUserDetails me, @PathVariable UserRole role, @RequestParam(value = "team", required = false) TeamType requiredTeam) {
// 1) 인증 체크
if (me == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.error(GlobalErrorCode.UNAUTHORIZED_USER.getStatus()
.value(), GlobalErrorCode.UNAUTHORIZED_USER.getMessage(), null));
}

if (UserRole.hasAtLeast(me.getRole(), role)) {
return ResponseEntity.ok(ApiResponse.ok("ROLE_CHECK_PASSED", null));
// 2) role check
final boolean roleOk = UserRole.hasAtLeast(me.getRole(), role);

// 3) team check if team parameter exists
boolean teamOk = false;
if (requiredTeam != null) {
if (UserRole.hasAtLeast(me.getRole(), UserRole.ORGANIZER)) {
teamOk = true;
} else {
teamOk = (me.getTeam() != null && me.getTeam() == requiredTeam);
}
}

// 4) OR 조건으로 최종 판정
if (roleOk || teamOk) {
return ResponseEntity.ok(ApiResponse.ok("ROLE_OR_TEAM_CHECK_PASSED", null));
}

return ResponseEntity.status(HttpStatus.FORBIDDEN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public ResponseEntity<ApiResponse<CreateResponse, Void>> create(
description = "전체 목록 또는 이름 검색 결과를 반환합니다.",
security = { @SecurityRequirement(name = "BearerAuth") }
)
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('LEAD', 'ORGANIZER', 'ADMIN')")
@GetMapping("/applicants")
public ResponseEntity<ApiResponse<java.util.List<CoreRecruitApplicantSummaryResponse>, PageMeta>> getApplicants(
@Parameter(description = "검색어(이름 부분 일치). 없으면 전체 조회", example = "홍길동")
Expand Down Expand Up @@ -89,7 +89,7 @@ public ResponseEntity<ApiResponse<java.util.List<CoreRecruitApplicantSummaryResp
summary = "코어 리쿠르트 지원자 상세 조회",
security = { @SecurityRequirement(name = "BearerAuth") }
)
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('LEAD', 'ORGANIZER', 'ADMIN')")
@GetMapping("/applicants/{id}")
public ResponseEntity<ApiResponse<CoreRecruitApplicantDetailResponse, Void>> getApplicant(
@PathVariable Long id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public ResponseEntity<ApiResponse<CheckPhoneNumberResponse, Void>> duplicatedPho
}

@Operation(summary = "특정 멤버 가입 신청서 조회", security = {@SecurityRequirement(name = "BearerAuth")})
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('LEAD','ORGANIZER','ADMIN') or T(inha.gdgoc.domain.user.enums.TeamType).HR == principal.team")
@GetMapping("/recruit/members/{memberId}")
public ResponseEntity<ApiResponse<SpecifiedMemberResponse, Void>> getSpecifiedMember(
@PathVariable Long memberId
Expand All @@ -100,7 +100,7 @@ public ResponseEntity<ApiResponse<SpecifiedMemberResponse, Void>> getSpecifiedMe
description = "설정하려는 상태(NOT 현재 상태)를 body에 보내주세요. true=입금 완료, false=입금 미완료",
security = { @SecurityRequirement(name = "BearerAuth") }
)
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('LEAD','ORGANIZER','ADMIN') or T(inha.gdgoc.domain.user.enums.TeamType).HR == principal.team")
@PatchMapping("/recruit/members/{memberId}/payment")
public ResponseEntity<ApiResponse<Void, Void>> updatePayment(
@PathVariable Long memberId,
Expand All @@ -122,7 +122,7 @@ public ResponseEntity<ApiResponse<Void, Void>> updatePayment(
description = "전체 목록 또는 이름 검색 결과를 반환합니다. 검색어(question)를 주면 이름 포함 검색, 없으면 전체 조회. sort랑 dir은 example 값 그대로 코딩하는 것 추천...",
security = { @SecurityRequirement(name = "BearerAuth") }
)
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('LEAD','ORGANIZER','ADMIN') or T(inha.gdgoc.domain.user.enums.TeamType).HR == principal.team")
@GetMapping("/recruit/members")
public ResponseEntity<ApiResponse<List<RecruitMemberSummaryResponse>, PageMeta>> getMembers(
@Parameter(description = "검색어(이름 부분 일치). 없으면 전체 조회", example = "소연")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package inha.gdgoc.domain.user.controller;

import inha.gdgoc.domain.user.dto.request.UpdateRoleRequest;
import inha.gdgoc.domain.user.dto.request.UpdateUserRoleTeamRequest;
import inha.gdgoc.domain.user.dto.response.UserSummaryResponse;
import inha.gdgoc.domain.user.service.UserAdminService;
import inha.gdgoc.global.config.jwt.TokenProvider.CustomUserDetails;
import inha.gdgoc.global.dto.response.ApiResponse;
import inha.gdgoc.global.dto.response.PageMeta;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/v1/admin/users")
public class UserAdminController {

private final UserAdminService userAdminService;

@Operation(summary = "사용자 요약 목록 조회", security = {@SecurityRequirement(name = "BearerAuth")})
@PreAuthorize("hasAnyRole('LEAD','ORGANIZER','ADMIN') or T(inha.gdgoc.domain.user.enums.TeamType).HR == principal.team")
@GetMapping
public ResponseEntity<ApiResponse<Page<UserSummaryResponse>, PageMeta>> list(@RequestParam(required = false) String q, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size, @RequestParam(defaultValue = "name") String sort, @RequestParam(defaultValue = "ASC") String dir) {
Sort.Direction direction = "ASC".equalsIgnoreCase(dir) ? Sort.Direction.ASC : Sort.Direction.DESC;
Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sort));

Page<UserSummaryResponse> result = userAdminService.listUsers(q, pageable);
return ResponseEntity.ok(ApiResponse.ok("USER_SUMMARY_LIST_RETRIEVED", result, PageMeta.of(result)));
}

@Operation(summary = "사용자 역할/팀 수정", security = {@SecurityRequirement(name = "BearerAuth")})
@PreAuthorize("hasAnyRole('LEAD','ORGANIZER','ADMIN')")
@PatchMapping("/{userId}/role-team")
public ResponseEntity<ApiResponse<Void, Void>> updateRoleTeam(@AuthenticationPrincipal CustomUserDetails me, @PathVariable Long userId, @RequestBody UpdateUserRoleTeamRequest req) {
userAdminService.updateRoleAndTeam(me, userId, req);
return ResponseEntity.ok(ApiResponse.ok("USER_ROLE_TEAM_UPDATED"));
}

@Operation(summary = "사용자 역할 수정", security = {@SecurityRequirement(name = "BearerAuth")})
@PatchMapping("/{userId}/role")
@PreAuthorize("hasAnyRole('LEAD','ORGANIZER','ADMIN') or T(inha.gdgoc.domain.user.enums.TeamType).HR == principal.team")
public ResponseEntity<ApiResponse<Void, Void>> updateUserRole(@AuthenticationPrincipal CustomUserDetails me, @PathVariable Long userId, @RequestBody @Valid UpdateRoleRequest req) {
userAdminService.updateUserRoleWithRules(me, userId, req.role());
return ResponseEntity.ok(ApiResponse.ok("USER_ROLE_UPDATED"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package inha.gdgoc.domain.user.dto.request;

import inha.gdgoc.domain.user.enums.UserRole;
import jakarta.validation.constraints.NotNull;

public record UpdateRoleRequest(
@NotNull UserRole role
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package inha.gdgoc.domain.user.dto.request;

import inha.gdgoc.domain.user.enums.TeamType;
import inha.gdgoc.domain.user.enums.UserRole;

public record UpdateUserRoleTeamRequest(
UserRole role, // null 이면 변경 안 함
TeamType team // null 이면 변경 안 함
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package inha.gdgoc.domain.user.dto.response;

import inha.gdgoc.domain.user.enums.TeamType;
import inha.gdgoc.domain.user.enums.UserRole;

public record UserSummaryResponse(
Long id,
String name,
String major,
String studentId,
String email,
UserRole userRole,
TeamType team
) {}
2 changes: 2 additions & 0 deletions src/main/java/inha/gdgoc/domain/user/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,6 @@ public void updatePassword(String password) throws NoSuchAlgorithmException, Inv
public boolean isGuest() {
return this.userRole == UserRole.GUEST;
}
public void changeRole(UserRole role) { this.userRole = role; }
public void changeTeam(TeamType team) { this.team = team; }
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package inha.gdgoc.domain.user.repository;

import inha.gdgoc.domain.user.dto.response.UserSummaryResponse;
import inha.gdgoc.domain.user.entity.User;
import inha.gdgoc.domain.user.enums.TeamType;
import inha.gdgoc.domain.user.enums.UserRole;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.Collection;
Expand All @@ -29,4 +35,15 @@ public interface UserRepository extends JpaRepository<User, Long>, UserRepositor

// 필요 시: 특정 팀 전체 멤버(역할 무관)
List<User> findByTeam(TeamType team);

@Query("""
select new inha.gdgoc.domain.user.dto.response.UserSummaryResponse(
u.id, u.name, u.major, u.studentId, u.email, u.userRole, u.team
)
from User u
where (:q is null or :q = '' or u.name like concat('%', :q, '%'))
""")
Page<UserSummaryResponse> findSummaries(@Param("q") String q, Pageable pageable);

@NotNull Optional<User> findById(@NotNull Long id);
}
Loading
Loading