From 109de38b58157301905d4659987c134256c01672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9B=90=EA=B2=BD?= <148550522+kwoo28@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:12:21 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EC=9C=A0=EC=A0=80=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20(#1?= =?UTF-8?q?068)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Owner 분리 * refactor: Student 패키지 분리 * refactor: User에 있는 정보 owner와 student로 리팩토링 * refactor: response 변수명 통일 * refactor: AdminOwnerService 리팩토링 * refactor: AdminOwnerService 리팩토링 * refactor: AdminOwnerService 리팩토링 * refactor: AdminStudentApi operation 수정 * refactor: 테스트 리팩토링 * refactor: User Validation 검증 패키지 추가 * refactor: 불필요한 import 제거 --------- Co-authored-by: 김원경 --- .../admin/owner/controller/AdminOwnerApi.java | 112 ++++ .../controller/AdminOwnerController.java | 74 +++ .../repository/AdminOwnerRepository.java | 2 +- .../AdminOwnerShopRedisRepository.java | 2 +- .../owner/service/AdminOwnerService.java | 141 +++++ .../student/controller/AdminStudentApi.java | 81 +++ .../controller/AdminStudentController.java | 61 ++ .../repository/AdminStudentRepository.java | 2 +- .../student/service/AdminStudentService.java | 76 +++ .../admin/user/controller/AdminUserApi.java | 133 ----- .../user/controller/AdminUserController.java | 88 +-- .../admin/user/service/AdminUserService.java | 229 +------- .../user/validation/AdminUserValidation.java | 60 ++ .../admin/acceptance/AdminOwnerApiTest.java | 325 +++++++++++ .../admin/acceptance/AdminStudentApiTest.java | 270 +++++++++ .../admin/acceptance/AdminUserApiTest.java | 526 ------------------ 16 files changed, 1217 insertions(+), 965 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/admin/owner/controller/AdminOwnerApi.java create mode 100644 src/main/java/in/koreatech/koin/admin/owner/controller/AdminOwnerController.java rename src/main/java/in/koreatech/koin/admin/{user => owner}/repository/AdminOwnerRepository.java (97%) rename src/main/java/in/koreatech/koin/admin/{user => owner}/repository/AdminOwnerShopRedisRepository.java (86%) create mode 100644 src/main/java/in/koreatech/koin/admin/owner/service/AdminOwnerService.java create mode 100644 src/main/java/in/koreatech/koin/admin/student/controller/AdminStudentApi.java create mode 100644 src/main/java/in/koreatech/koin/admin/student/controller/AdminStudentController.java rename src/main/java/in/koreatech/koin/admin/{user => student}/repository/AdminStudentRepository.java (96%) create mode 100644 src/main/java/in/koreatech/koin/admin/student/service/AdminStudentService.java create mode 100644 src/main/java/in/koreatech/koin/admin/user/validation/AdminUserValidation.java create mode 100644 src/test/java/in/koreatech/koin/admin/acceptance/AdminOwnerApiTest.java create mode 100644 src/test/java/in/koreatech/koin/admin/acceptance/AdminStudentApiTest.java diff --git a/src/main/java/in/koreatech/koin/admin/owner/controller/AdminOwnerApi.java b/src/main/java/in/koreatech/koin/admin/owner/controller/AdminOwnerApi.java new file mode 100644 index 000000000..068efe843 --- /dev/null +++ b/src/main/java/in/koreatech/koin/admin/owner/controller/AdminOwnerApi.java @@ -0,0 +1,112 @@ +package in.koreatech.koin.admin.owner.controller; + +import static in.koreatech.koin.domain.user.model.UserType.ADMIN; + +import org.springdoc.core.annotations.ParameterObject; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import in.koreatech.koin.admin.user.dto.AdminNewOwnersResponse; +import in.koreatech.koin.admin.user.dto.AdminOwnerResponse; +import in.koreatech.koin.admin.user.dto.AdminOwnerUpdateRequest; +import in.koreatech.koin.admin.user.dto.AdminOwnerUpdateResponse; +import in.koreatech.koin.admin.user.dto.AdminOwnersResponse; +import in.koreatech.koin.admin.user.dto.OwnersCondition; +import in.koreatech.koin.global.auth.Auth; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +@Tag(name = "(Admin) Owner: 사장님", description = "관리자 권한으로 사장님 정보를 관리한다") +public interface AdminOwnerApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "사장님 권한 요청 허용") + @SecurityRequirement(name = "Jwt Authentication") + @PutMapping("/admin/owner/{id}/authed") + ResponseEntity allowOwnerPermission( + @PathVariable Integer id, + @Auth(permit = {ADMIN}) Integer adminId + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "특정 사장님 조회") + @SecurityRequirement(name = "Jwt Authentication") + @GetMapping("/admin/users/owner/{id}") + ResponseEntity getOwner( + @PathVariable Integer id, + @Auth(permit = {ADMIN}) Integer adminId + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "특정 사장님 수정") + @SecurityRequirement(name = "Jwt Authentication") + @PutMapping("/admin/users/owner/{id}") + ResponseEntity updateOwner( + @PathVariable Integer id, + @RequestBody @Valid AdminOwnerUpdateRequest request, + @Auth(permit = {ADMIN}) Integer adminId + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))) + } + ) + @Operation(summary = "가입 신청한 사장님 리스트 조회 (페이지네이션)") + @SecurityRequirement(name = "Jwt Authentication") + @GetMapping("/admin/users/new-owners") + ResponseEntity getNewOwners( + @ParameterObject @ModelAttribute OwnersCondition ownersCondition, + @Auth(permit = {ADMIN}) Integer adminId + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))) + } + ) + @Operation(summary = "사장 리스트 조회 (페이지네이션)") + @SecurityRequirement(name = "Jwt Authentication") + @GetMapping("/admin/users/owners") + ResponseEntity getOwners( + @ParameterObject @ModelAttribute OwnersCondition ownersCondition, + @Auth(permit = {ADMIN}) Integer adminId + ); +} diff --git a/src/main/java/in/koreatech/koin/admin/owner/controller/AdminOwnerController.java b/src/main/java/in/koreatech/koin/admin/owner/controller/AdminOwnerController.java new file mode 100644 index 000000000..1696c327e --- /dev/null +++ b/src/main/java/in/koreatech/koin/admin/owner/controller/AdminOwnerController.java @@ -0,0 +1,74 @@ +package in.koreatech.koin.admin.owner.controller; + +import static in.koreatech.koin.domain.user.model.UserType.ADMIN; + +import org.springdoc.core.annotations.ParameterObject; +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.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import in.koreatech.koin.admin.owner.service.AdminOwnerService; +import in.koreatech.koin.admin.user.dto.AdminNewOwnersResponse; +import in.koreatech.koin.admin.user.dto.AdminOwnerResponse; +import in.koreatech.koin.admin.user.dto.AdminOwnerUpdateRequest; +import in.koreatech.koin.admin.user.dto.AdminOwnerUpdateResponse; +import in.koreatech.koin.admin.user.dto.AdminOwnersResponse; +import in.koreatech.koin.admin.user.dto.OwnersCondition; +import in.koreatech.koin.global.auth.Auth; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class AdminOwnerController implements AdminOwnerApi{ + + private final AdminOwnerService adminOwnerService; + + @PutMapping("/admin/owner/{id}/authed") + public ResponseEntity allowOwnerPermission( + @PathVariable Integer id, + @Auth(permit = {ADMIN}) Integer adminId) { + adminOwnerService.allowOwnerPermission(id); + return ResponseEntity.ok().build(); + } + + @GetMapping("/admin/users/owner/{id}") + public ResponseEntity getOwner( + @PathVariable Integer id, + @Auth(permit = {ADMIN}) Integer adminId + ) { + AdminOwnerResponse response = adminOwnerService.getOwner(id); + return ResponseEntity.ok().body(response); + } + + @PutMapping("/admin/users/owner/{id}") + public ResponseEntity updateOwner( + @PathVariable Integer id, + @RequestBody @Valid AdminOwnerUpdateRequest request, + @Auth(permit = {ADMIN}) Integer adminId + ) { + AdminOwnerUpdateResponse response = adminOwnerService.updateOwner(id, request); + return ResponseEntity.ok().body(response); + } + + @GetMapping("/admin/users/new-owners") + public ResponseEntity getNewOwners( + @ParameterObject @ModelAttribute OwnersCondition ownersCondition, + @Auth(permit = {ADMIN}) Integer adminId + ) { + return ResponseEntity.ok().body(adminOwnerService.getNewOwners(ownersCondition)); + } + + @GetMapping("/admin/users/owners") + public ResponseEntity getOwners( + @ParameterObject @ModelAttribute OwnersCondition ownersCondition, + @Auth(permit = {ADMIN}) Integer adminId + ) { + return ResponseEntity.ok().body(adminOwnerService.getOwners(ownersCondition)); + } + +} diff --git a/src/main/java/in/koreatech/koin/admin/user/repository/AdminOwnerRepository.java b/src/main/java/in/koreatech/koin/admin/owner/repository/AdminOwnerRepository.java similarity index 97% rename from src/main/java/in/koreatech/koin/admin/user/repository/AdminOwnerRepository.java rename to src/main/java/in/koreatech/koin/admin/owner/repository/AdminOwnerRepository.java index 1ebc65c9c..86e5ca379 100644 --- a/src/main/java/in/koreatech/koin/admin/user/repository/AdminOwnerRepository.java +++ b/src/main/java/in/koreatech/koin/admin/owner/repository/AdminOwnerRepository.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.admin.user.repository; +package in.koreatech.koin.admin.owner.repository; import java.util.Optional; diff --git a/src/main/java/in/koreatech/koin/admin/user/repository/AdminOwnerShopRedisRepository.java b/src/main/java/in/koreatech/koin/admin/owner/repository/AdminOwnerShopRedisRepository.java similarity index 86% rename from src/main/java/in/koreatech/koin/admin/user/repository/AdminOwnerShopRedisRepository.java rename to src/main/java/in/koreatech/koin/admin/owner/repository/AdminOwnerShopRedisRepository.java index 7b95e3371..765b309ec 100644 --- a/src/main/java/in/koreatech/koin/admin/user/repository/AdminOwnerShopRedisRepository.java +++ b/src/main/java/in/koreatech/koin/admin/owner/repository/AdminOwnerShopRedisRepository.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.admin.user.repository; +package in.koreatech.koin.admin.owner.repository; import java.util.Optional; diff --git a/src/main/java/in/koreatech/koin/admin/owner/service/AdminOwnerService.java b/src/main/java/in/koreatech/koin/admin/owner/service/AdminOwnerService.java new file mode 100644 index 000000000..9cecd4a08 --- /dev/null +++ b/src/main/java/in/koreatech/koin/admin/owner/service/AdminOwnerService.java @@ -0,0 +1,141 @@ +package in.koreatech.koin.admin.owner.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.koin.admin.owner.repository.AdminOwnerRepository; +import in.koreatech.koin.admin.owner.repository.AdminOwnerShopRedisRepository; +import in.koreatech.koin.admin.shop.repository.AdminShopRepository; +import in.koreatech.koin.admin.user.dto.AdminNewOwnersResponse; +import in.koreatech.koin.admin.user.dto.AdminOwnerResponse; +import in.koreatech.koin.admin.user.dto.AdminOwnerUpdateRequest; +import in.koreatech.koin.admin.user.dto.AdminOwnerUpdateResponse; +import in.koreatech.koin.admin.user.dto.AdminOwnersResponse; +import in.koreatech.koin.admin.user.dto.OwnersCondition; +import in.koreatech.koin.admin.user.repository.AdminUserRepository; +import in.koreatech.koin.domain.owner.model.Owner; +import in.koreatech.koin.domain.owner.model.OwnerIncludingShop; +import in.koreatech.koin.domain.owner.model.OwnerShop; +import in.koreatech.koin.domain.shop.model.shop.Shop; +import in.koreatech.koin.domain.user.model.UserType; +import in.koreatech.koin.global.model.Criteria; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AdminOwnerService { + + private final AdminOwnerRepository adminOwnerRepository; + private final AdminOwnerShopRedisRepository adminOwnerShopRedisRepository; + private final AdminShopRepository adminShopRepository; + private final AdminUserRepository adminUserRepository; + + @Transactional + public void allowOwnerPermission(Integer id) { + Owner owner = adminOwnerRepository.getById(id); + owner.getUser().auth(); + adminOwnerShopRedisRepository.findById(id).ifPresent(ownerShop -> { + Integer shopId = ownerShop.getShopId(); + if (shopId != null) { + Shop shop = adminShopRepository.getById(shopId); + shop.updateOwner(owner); + owner.setGrantShop(true); + } + adminOwnerShopRedisRepository.deleteById(id); + }); + } + + public AdminOwnerResponse getOwner(Integer ownerId) { + Owner owner = adminOwnerRepository.getById(ownerId); + + List shopsId = adminShopRepository.findAllByOwnerId(ownerId) + .stream() + .map(Shop::getId) + .toList(); + + return AdminOwnerResponse.of(owner, shopsId); + } + + @Transactional + public AdminOwnerUpdateResponse updateOwner(Integer ownerId, AdminOwnerUpdateRequest request) { + Owner owner = adminOwnerRepository.getById(ownerId); + owner.update(request); + return AdminOwnerUpdateResponse.from(owner); + } + + public AdminNewOwnersResponse getNewOwners(OwnersCondition ownersCondition) { + ownersCondition.checkDataConstraintViolation(); + + Integer totalOwners = adminUserRepository.findUsersCountByUserTypeAndIsAuthed(UserType.OWNER, false); + Criteria criteria = Criteria.of(ownersCondition.page(), ownersCondition.limit(), totalOwners); + Sort.Direction direction = ownersCondition.getDirection(); + + Page result = getNewOwnersResultPage(ownersCondition, criteria, direction); + + List ownerIncludingShops = new ArrayList<>(); + for (Owner owner: result.getContent()) { + adminOwnerShopRedisRepository.findById(owner.getId()).ifPresent(ownerShop -> { + Shop shop = adminShopRepository.findById(ownerShop.getShopId()).orElse(null); + OwnerIncludingShop ownerIncludingShop = OwnerIncludingShop.of(owner, shop); + ownerIncludingShops.add(ownerIncludingShop); + }); + } + + return AdminNewOwnersResponse.of(ownerIncludingShops, result, criteria); + } + + public AdminOwnersResponse getOwners(OwnersCondition ownersCondition) { + ownersCondition.checkDataConstraintViolation(); + + Integer totalOwners = adminUserRepository.findUsersCountByUserTypeAndIsAuthed(UserType.OWNER, true); + Criteria criteria = Criteria.of(ownersCondition.page(), ownersCondition.limit(), totalOwners); + Sort.Direction direction = ownersCondition.getDirection(); + + Page result = getOwnersResultPage(ownersCondition, criteria, direction); + + return AdminOwnersResponse.of(result, criteria); + } + + private Page getNewOwnersResultPage(OwnersCondition ownersCondition, Criteria criteria, + Sort.Direction direction) { + PageRequest pageRequest = PageRequest.of( + criteria.getPage(), + criteria.getLimit(), + Sort.by(direction, "user.createdAt") + ); + + if (ownersCondition.searchType() == OwnersCondition.SearchType.EMAIL) { + return adminOwnerRepository.findPageUnauthenticatedOwnersByEmail(ownersCondition.query(), pageRequest); + } + if (ownersCondition.searchType() == OwnersCondition.SearchType.NAME) { + return adminOwnerRepository.findPageUnauthenticatedOwnersByName(ownersCondition.query(), pageRequest); + } + return adminOwnerRepository.findPageUnauthenticatedOwners(pageRequest); + } + + private Page getOwnersResultPage(OwnersCondition ownersCondition, Criteria criteria, + Sort.Direction direction) { + PageRequest pageRequest = PageRequest.of( + criteria.getPage(), + criteria.getLimit(), + Sort.by(direction, "user.createdAt") + ); + + if (ownersCondition.searchType() == OwnersCondition.SearchType.EMAIL) { + return adminOwnerRepository.findPageOwnersByEmail(ownersCondition.query(), pageRequest); + } + if (ownersCondition.searchType() == OwnersCondition.SearchType.NAME) { + return adminOwnerRepository.findPageOwnersByName(ownersCondition.query(), pageRequest); + } + return adminOwnerRepository.findPageOwners(pageRequest); + } +} diff --git a/src/main/java/in/koreatech/koin/admin/student/controller/AdminStudentApi.java b/src/main/java/in/koreatech/koin/admin/student/controller/AdminStudentApi.java new file mode 100644 index 000000000..09cd1b7a3 --- /dev/null +++ b/src/main/java/in/koreatech/koin/admin/student/controller/AdminStudentApi.java @@ -0,0 +1,81 @@ +package in.koreatech.koin.admin.student.controller; + +import static in.koreatech.koin.domain.user.model.UserType.ADMIN; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import in.koreatech.koin.admin.user.dto.AdminStudentResponse; +import in.koreatech.koin.admin.user.dto.AdminStudentUpdateRequest; +import in.koreatech.koin.admin.user.dto.AdminStudentUpdateResponse; +import in.koreatech.koin.admin.user.dto.AdminStudentsResponse; +import in.koreatech.koin.global.auth.Auth; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +@Tag(name = "(Admin) Student: 학생", description = "관리자 권한으로 학생 정보를 관리한다") +public interface AdminStudentApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "학생 리스트 조회(페이지네이션)") + @SecurityRequirement(name = "Jwt Authentication") + @GetMapping("/admin/students") + ResponseEntity getStudents( + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer limit, + @RequestParam(required = false) Boolean isAuthed, + @RequestParam(required = false) String nickname, + @RequestParam(required = false) String email, + @Auth(permit = {ADMIN}) Integer adminId + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "학생 정보 조회") + @SecurityRequirement(name = "Jwt Authentication") + @GetMapping("/admin/users/student/{id}") + ResponseEntity getStudent( + @PathVariable Integer id, + @Auth(permit = {ADMIN}) Integer adminId + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), + @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), + } + ) + @Operation(summary = "학생 정보 수정") + @SecurityRequirement(name = "Jwt Authentication") + @PutMapping("/admin/users/student/{id}") + ResponseEntity updateStudent( + @Valid @RequestBody AdminStudentUpdateRequest adminRequest, + @PathVariable Integer id, + @Auth(permit = {ADMIN}) Integer adminId + ); +} diff --git a/src/main/java/in/koreatech/koin/admin/student/controller/AdminStudentController.java b/src/main/java/in/koreatech/koin/admin/student/controller/AdminStudentController.java new file mode 100644 index 000000000..a99050e05 --- /dev/null +++ b/src/main/java/in/koreatech/koin/admin/student/controller/AdminStudentController.java @@ -0,0 +1,61 @@ +package in.koreatech.koin.admin.student.controller; + +import static in.koreatech.koin.domain.user.model.UserType.ADMIN; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import in.koreatech.koin.admin.student.service.AdminStudentService; +import in.koreatech.koin.admin.user.dto.AdminStudentResponse; +import in.koreatech.koin.admin.user.dto.AdminStudentUpdateRequest; +import in.koreatech.koin.admin.user.dto.AdminStudentUpdateResponse; +import in.koreatech.koin.admin.user.dto.AdminStudentsResponse; +import in.koreatech.koin.admin.user.dto.StudentsCondition; +import in.koreatech.koin.global.auth.Auth; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class AdminStudentController implements AdminStudentApi{ + + private final AdminStudentService adminStudentService; + + @GetMapping("/admin/students") + public ResponseEntity getStudents( + @RequestParam(required = false) Integer page, + @RequestParam(required = false) Integer limit, + @RequestParam(required = false) Boolean isAuthed, + @RequestParam(required = false) String nickname, + @RequestParam(required = false) String email, + @Auth(permit = {ADMIN}) Integer adminId + ) { + StudentsCondition studentsCondition = new StudentsCondition(page, limit, isAuthed, nickname, email); + AdminStudentsResponse response = adminStudentService.getStudents(studentsCondition); + return ResponseEntity.ok().body(response); + } + + @GetMapping("/admin/users/student/{id}") + public ResponseEntity getStudent( + @PathVariable Integer id, + @Auth(permit = {ADMIN}) Integer adminId + ) { + AdminStudentResponse response = adminStudentService.getStudent(id); + return ResponseEntity.ok().body(response); + } + + @PutMapping("/admin/users/student/{id}") + public ResponseEntity updateStudent( + @Valid @RequestBody AdminStudentUpdateRequest adminRequest, + @PathVariable Integer id, + @Auth(permit = {ADMIN}) Integer adminId + ) { + AdminStudentUpdateResponse response = adminStudentService.updateStudent(id, adminRequest); + return ResponseEntity.ok().body(response); + } +} diff --git a/src/main/java/in/koreatech/koin/admin/user/repository/AdminStudentRepository.java b/src/main/java/in/koreatech/koin/admin/student/repository/AdminStudentRepository.java similarity index 96% rename from src/main/java/in/koreatech/koin/admin/user/repository/AdminStudentRepository.java rename to src/main/java/in/koreatech/koin/admin/student/repository/AdminStudentRepository.java index db32b0ddf..22f54636b 100644 --- a/src/main/java/in/koreatech/koin/admin/user/repository/AdminStudentRepository.java +++ b/src/main/java/in/koreatech/koin/admin/student/repository/AdminStudentRepository.java @@ -1,4 +1,4 @@ -package in.koreatech.koin.admin.user.repository; +package in.koreatech.koin.admin.student.repository; import java.util.Optional; diff --git a/src/main/java/in/koreatech/koin/admin/student/service/AdminStudentService.java b/src/main/java/in/koreatech/koin/admin/student/service/AdminStudentService.java new file mode 100644 index 000000000..18eee6030 --- /dev/null +++ b/src/main/java/in/koreatech/koin/admin/student/service/AdminStudentService.java @@ -0,0 +1,76 @@ +package in.koreatech.koin.admin.student.service; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.koin.admin.student.repository.AdminStudentRepository; +import in.koreatech.koin.admin.user.dto.AdminStudentResponse; +import in.koreatech.koin.admin.user.dto.AdminStudentUpdateRequest; +import in.koreatech.koin.admin.user.dto.AdminStudentUpdateResponse; +import in.koreatech.koin.admin.user.dto.AdminStudentsResponse; +import in.koreatech.koin.admin.user.dto.StudentsCondition; +import in.koreatech.koin.admin.user.repository.AdminUserRepository; +import in.koreatech.koin.domain.student.exception.StudentDepartmentNotValidException; +import in.koreatech.koin.domain.student.model.Student; +import in.koreatech.koin.domain.student.model.StudentDepartment; +import in.koreatech.koin.domain.user.exception.DuplicationNicknameException; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.domain.user.model.UserGender; +import in.koreatech.koin.global.model.Criteria; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AdminStudentService { + + private final AdminStudentRepository adminStudentRepository; + private final AdminUserRepository adminUserRepository; + private final PasswordEncoder passwordEncoder; + + public AdminStudentsResponse getStudents(StudentsCondition studentsCondition) { + Integer totalStudents = adminStudentRepository.findAllStudentCount(); + Criteria criteria = Criteria.of(studentsCondition.page(), studentsCondition.limit(), totalStudents); + + PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit()); + Page studentsPage = adminStudentRepository.findByConditions(studentsCondition, pageRequest); + + return AdminStudentsResponse.from(studentsPage); + } + + public AdminStudentResponse getStudent(Integer userId) { + Student student = adminStudentRepository.getById(userId); + return AdminStudentResponse.from(student); + } + + @Transactional + public AdminStudentUpdateResponse updateStudent(Integer id, AdminStudentUpdateRequest adminRequest) { + Student student = adminStudentRepository.getById(id); + User user = student.getUser(); + validateNicknameDuplication(adminRequest.nickname(), id); + validateDepartmentValid(adminRequest.major()); + user.update(adminRequest.nickname(), adminRequest.name(), + adminRequest.phoneNumber(), UserGender.from(adminRequest.gender())); + user.updateStudentPassword(passwordEncoder, adminRequest.password()); + student.update(adminRequest.studentNumber(), adminRequest.major()); + adminStudentRepository.save(student); + + return AdminStudentUpdateResponse.from(student); + } + + private void validateNicknameDuplication(String nickname, Integer userId) { + if (nickname != null && + adminUserRepository.existsByNicknameAndIdNot(nickname, userId)) { + throw DuplicationNicknameException.withDetail("nickname : " + nickname); + } + } + + private void validateDepartmentValid(String department) { + if (department != null && !StudentDepartment.isValid(department)) { + throw StudentDepartmentNotValidException.withDetail("학부(학과) : " + department); + } + } +} diff --git a/src/main/java/in/koreatech/koin/admin/user/controller/AdminUserApi.java b/src/main/java/in/koreatech/koin/admin/user/controller/AdminUserApi.java index b1bb87f0e..68d3c64fd 100644 --- a/src/main/java/in/koreatech/koin/admin/user/controller/AdminUserApi.java +++ b/src/main/java/in/koreatech/koin/admin/user/controller/AdminUserApi.java @@ -48,25 +48,6 @@ @Tag(name = "(Admin) User: 회원", description = "관리자 권한으로 회원 정보를 관리한다") public interface AdminUserApi { - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "학생 리스트 조회(페이지네이션)") - @SecurityRequirement(name = "Jwt Authentication") - @GetMapping("/admin/students") - ResponseEntity getStudents( - @RequestParam(required = false) Integer page, - @RequestParam(required = false) Integer limit, - @RequestParam(required = false) Boolean isAuthed, - @RequestParam(required = false) String nickname, - @RequestParam(required = false) String email, - @Auth(permit = {ADMIN}) Integer adminId - ); @ApiResponses( value = { @@ -227,120 +208,6 @@ ResponseEntity updateAdminPermission( @Auth(permit = {ADMIN}) Integer adminId ); - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "사장님 권한 요청 허용") - @SecurityRequirement(name = "Jwt Authentication") - @PutMapping("/admin/owner/{id}/authed") - ResponseEntity allowOwnerPermission( - @PathVariable Integer id, - @Auth(permit = {ADMIN}) Integer adminId - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "회원 정보 조회") - @SecurityRequirement(name = "Jwt Authentication") - @GetMapping("/admin/users/student/{id}") - ResponseEntity getStudent( - @PathVariable Integer id, - @Auth(permit = {ADMIN}) Integer adminId - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "회원 정보 수정") - @SecurityRequirement(name = "Jwt Authentication") - @PutMapping("/admin/users/student/{id}") - ResponseEntity updateStudent( - @Valid @RequestBody AdminStudentUpdateRequest adminRequest, - @PathVariable Integer id, - @Auth(permit = {ADMIN}) Integer adminId - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "특정 사장님 조회") - @SecurityRequirement(name = "Jwt Authentication") - @GetMapping("/admin/users/owner/{id}") - ResponseEntity getOwner( - @PathVariable Integer id, - @Auth(permit = {ADMIN}) Integer adminId - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))), - } - ) - @Operation(summary = "특정 사장님 수정") - @SecurityRequirement(name = "Jwt Authentication") - @PutMapping("/admin/users/owner/{id}") - ResponseEntity updateOwner( - @PathVariable Integer id, - @RequestBody @Valid AdminOwnerUpdateRequest request, - @Auth(permit = {ADMIN}) Integer adminId - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))) - } - ) - @Operation(summary = "가입 신청한 사장님 리스트 조회 (페이지네이션)") - @SecurityRequirement(name = "Jwt Authentication") - @GetMapping("/admin/users/new-owners") - ResponseEntity getNewOwners( - @ParameterObject @ModelAttribute OwnersCondition ownersCondition, - @Auth(permit = {ADMIN}) Integer adminId - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))), - @ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))) - } - ) - @Operation(summary = "사장 리스트 조회 (페이지네이션)") - @SecurityRequirement(name = "Jwt Authentication") - @GetMapping("/admin/users/owners") - ResponseEntity getOwners( - @ParameterObject @ModelAttribute OwnersCondition ownersCondition, - @Auth(permit = {ADMIN}) Integer adminId - ); - @ApiResponses( value = { @ApiResponse(responseCode = "200"), diff --git a/src/main/java/in/koreatech/koin/admin/user/controller/AdminUserController.java b/src/main/java/in/koreatech/koin/admin/user/controller/AdminUserController.java index 8c1a50eae..93f5b2d93 100644 --- a/src/main/java/in/koreatech/koin/admin/user/controller/AdminUserController.java +++ b/src/main/java/in/koreatech/koin/admin/user/controller/AdminUserController.java @@ -52,28 +52,6 @@ public class AdminUserController implements AdminUserApi{ private final AdminUserService adminUserService; - @PutMapping("/admin/owner/{id}/authed") - public ResponseEntity allowOwnerPermission( - @PathVariable Integer id, - @Auth(permit = {ADMIN}) Integer adminId) { - adminUserService.allowOwnerPermission(id); - return ResponseEntity.ok().build(); - } - - @GetMapping("/admin/students") - public ResponseEntity getStudents( - @RequestParam(required = false) Integer page, - @RequestParam(required = false) Integer limit, - @RequestParam(required = false) Boolean isAuthed, - @RequestParam(required = false) String nickname, - @RequestParam(required = false) String email, - @Auth(permit = {ADMIN}) Integer adminId - ) { - StudentsCondition studentsCondition = new StudentsCondition(page, limit, isAuthed, nickname, email); - AdminStudentsResponse adminStudentsResponse = adminUserService.getStudents(studentsCondition); - return ResponseEntity.ok().body(adminStudentsResponse); - } - @PostMapping("/admin") public ResponseEntity createAdmin( @RequestBody @Valid CreateAdminRequest request, @@ -113,9 +91,9 @@ public ResponseEntity logout( public ResponseEntity refresh( @RequestBody @Valid AdminTokenRefreshRequest request ) { - AdminTokenRefreshResponse tokenGroupResponse = adminUserService.adminRefresh(request); + AdminTokenRefreshResponse response = adminUserService.adminRefresh(request); return ResponseEntity.created(URI.create("/")) - .body(tokenGroupResponse); + .body(response); } @GetMapping("/admin/{id}") @@ -123,8 +101,8 @@ public ResponseEntity getAdmin( @PathVariable("id") Integer id, @Auth(permit = {ADMIN}) Integer adminId ) { - AdminResponse adminResponse = adminUserService.getAdmin(id); - return ResponseEntity.ok(adminResponse); + AdminResponse response = adminUserService.getAdmin(id); + return ResponseEntity.ok(response); } @GetMapping("/admins") @@ -137,8 +115,8 @@ public ResponseEntity getAdmins( @Auth(permit = {ADMIN}) Integer adminId ) { AdminsCondition adminsCondition = new AdminsCondition(page, limit, isAuthed, trackName, teamName); - AdminsResponse adminsResponse = adminUserService.getAdmins(adminsCondition); - return ResponseEntity.ok(adminsResponse); + AdminsResponse response = adminUserService.getAdmins(adminsCondition); + return ResponseEntity.ok(response); } @PutMapping("/admin/{id}/authed") @@ -170,60 +148,6 @@ public ResponseEntity updateAdminPermission( return ResponseEntity.ok().build(); } - @GetMapping("/admin/users/student/{id}") - public ResponseEntity getStudent( - @PathVariable Integer id, - @Auth(permit = {ADMIN}) Integer adminId - ) { - AdminStudentResponse adminStudentResponse = adminUserService.getStudent(id); - return ResponseEntity.ok().body(adminStudentResponse); - } - - @PutMapping("/admin/users/student/{id}") - public ResponseEntity updateStudent( - @Valid @RequestBody AdminStudentUpdateRequest adminRequest, - @PathVariable Integer id, - @Auth(permit = {ADMIN}) Integer adminId - ) { - AdminStudentUpdateResponse adminStudentUpdateResponse = adminUserService.updateStudent(id, adminRequest); - return ResponseEntity.ok().body(adminStudentUpdateResponse); - } - - @GetMapping("/admin/users/owner/{id}") - public ResponseEntity getOwner( - @PathVariable Integer id, - @Auth(permit = {ADMIN}) Integer adminId - ) { - AdminOwnerResponse adminOwnerResponse = adminUserService.getOwner(id); - return ResponseEntity.ok().body(adminOwnerResponse); - } - - @PutMapping("/admin/users/owner/{id}") - public ResponseEntity updateOwner( - @PathVariable Integer id, - @RequestBody @Valid AdminOwnerUpdateRequest request, - @Auth(permit = {ADMIN}) Integer adminId - ) { - AdminOwnerUpdateResponse adminOwnerUpdateResponse = adminUserService.updateOwner(id, request); - return ResponseEntity.ok().body(adminOwnerUpdateResponse); - } - - @GetMapping("/admin/users/new-owners") - public ResponseEntity getNewOwners( - @ParameterObject @ModelAttribute OwnersCondition ownersCondition, - @Auth(permit = {ADMIN}) Integer adminId - ) { - return ResponseEntity.ok().body(adminUserService.getNewOwners(ownersCondition)); - } - - @GetMapping("/admin/users/owners") - public ResponseEntity getOwners( - @ParameterObject @ModelAttribute OwnersCondition ownersCondition, - @Auth(permit = {ADMIN}) Integer adminId - ) { - return ResponseEntity.ok().body(adminUserService.getOwners(ownersCondition)); - } - @GetMapping("/admin/users/{id}") public ResponseEntity getUser( @PathVariable Integer id, diff --git a/src/main/java/in/koreatech/koin/admin/user/service/AdminUserService.java b/src/main/java/in/koreatech/koin/admin/user/service/AdminUserService.java index 287b2e30e..646c3a2ea 100644 --- a/src/main/java/in/koreatech/koin/admin/user/service/AdminUserService.java +++ b/src/main/java/in/koreatech/koin/admin/user/service/AdminUserService.java @@ -1,68 +1,38 @@ package in.koreatech.koin.admin.user.service; -import static in.koreatech.koin.domain.user.model.UserType.ADMIN; - import java.time.LocalDateTime; -import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.UUID; -import java.util.stream.Collectors; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import in.koreatech.koin.admin.shop.repository.AdminShopRepository; import in.koreatech.koin.admin.user.dto.AdminLoginRequest; import in.koreatech.koin.admin.user.dto.AdminLoginResponse; -import in.koreatech.koin.admin.user.dto.AdminNewOwnersResponse; -import in.koreatech.koin.admin.user.dto.AdminOwnerResponse; -import in.koreatech.koin.admin.user.dto.AdminOwnerUpdateRequest; -import in.koreatech.koin.admin.user.dto.AdminOwnerUpdateResponse; -import in.koreatech.koin.admin.user.dto.AdminOwnersResponse; import in.koreatech.koin.admin.user.dto.AdminPasswordChangeRequest; import in.koreatech.koin.admin.user.dto.AdminPermissionUpdateRequest; import in.koreatech.koin.admin.user.dto.AdminResponse; -import in.koreatech.koin.admin.user.dto.AdminStudentResponse; -import in.koreatech.koin.admin.user.dto.AdminStudentUpdateRequest; -import in.koreatech.koin.admin.user.dto.AdminStudentUpdateResponse; -import in.koreatech.koin.admin.user.dto.AdminStudentsResponse; import in.koreatech.koin.admin.user.dto.AdminTokenRefreshRequest; import in.koreatech.koin.admin.user.dto.AdminTokenRefreshResponse; import in.koreatech.koin.admin.user.dto.AdminUpdateRequest; import in.koreatech.koin.admin.user.dto.AdminsCondition; import in.koreatech.koin.admin.user.dto.AdminsResponse; import in.koreatech.koin.admin.user.dto.CreateAdminRequest; -import in.koreatech.koin.admin.user.dto.OwnersCondition; -import in.koreatech.koin.admin.user.dto.StudentsCondition; import in.koreatech.koin.admin.user.model.Admin; -import in.koreatech.koin.admin.user.repository.AdminOwnerRepository; -import in.koreatech.koin.admin.user.repository.AdminOwnerShopRedisRepository; +import in.koreatech.koin.admin.owner.repository.AdminOwnerRepository; import in.koreatech.koin.admin.user.repository.AdminRepository; -import in.koreatech.koin.admin.user.repository.AdminStudentRepository; +import in.koreatech.koin.admin.student.repository.AdminStudentRepository; import in.koreatech.koin.admin.user.repository.AdminTokenRepository; import in.koreatech.koin.admin.user.repository.AdminUserRepository; -import in.koreatech.koin.domain.owner.model.Owner; -import in.koreatech.koin.domain.owner.model.OwnerIncludingShop; -import in.koreatech.koin.domain.owner.model.OwnerShop; -import in.koreatech.koin.domain.shop.model.shop.Shop; -import in.koreatech.koin.domain.student.exception.StudentDepartmentNotValidException; -import in.koreatech.koin.domain.student.model.Student; -import in.koreatech.koin.domain.student.model.StudentDepartment; -import in.koreatech.koin.domain.user.exception.DuplicationNicknameException; -import in.koreatech.koin.domain.user.exception.UserNotFoundException; +import in.koreatech.koin.admin.user.validation.AdminUserValidation; import in.koreatech.koin.domain.user.model.User; -import in.koreatech.koin.domain.user.model.UserGender; import in.koreatech.koin.domain.user.model.UserToken; import in.koreatech.koin.domain.user.model.UserType; import in.koreatech.koin.global.auth.JwtProvider; import in.koreatech.koin.global.auth.exception.AuthorizationException; -import in.koreatech.koin.global.domain.email.exception.DuplicationEmailException; -import in.koreatech.koin.global.domain.email.model.EmailAddress; import in.koreatech.koin.global.exception.KoinIllegalArgumentException; import in.koreatech.koin.global.model.Criteria; import lombok.RequiredArgsConstructor; @@ -75,22 +45,12 @@ public class AdminUserService { private final JwtProvider jwtProvider; private final AdminStudentRepository adminStudentRepository; private final AdminOwnerRepository adminOwnerRepository; - private final AdminOwnerShopRedisRepository adminOwnerShopRedisRepository; private final AdminUserRepository adminUserRepository; - private final AdminShopRepository adminShopRepository; private final PasswordEncoder passwordEncoder; private final AdminTokenRepository adminTokenRepository; private final AdminRepository adminRepository; + private final AdminUserValidation adminUserValidation; - public AdminStudentsResponse getStudents(StudentsCondition studentsCondition) { - Integer totalStudents = adminStudentRepository.findAllStudentCount(); - Criteria criteria = Criteria.of(studentsCondition.page(), studentsCondition.limit(), totalStudents); - - PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit()); - Page studentsPage = adminStudentRepository.findByConditions(studentsCondition, pageRequest); - - return AdminStudentsResponse.from(studentsPage); - } @Transactional public AdminResponse createAdmin(CreateAdminRequest request, Integer adminId) { @@ -99,27 +59,12 @@ public AdminResponse createAdmin(CreateAdminRequest request, Integer adminId) { throw new AuthorizationException("어드민 계정 생성 권한이 없습니다."); } - validateAdminCreate(request); + adminUserValidation.validateEmailForAdminCreated(request.email()); Admin createAdmin = adminRepository.save(request.toEntity(passwordEncoder)); return AdminResponse.from(createAdmin); } - private void validateAdminCreate(CreateAdminRequest request) { - EmailAddress emailAddress = EmailAddress.from(request.email()); - emailAddress.validateKoreatechEmail(); - emailAddress.validateAdminEmail(); - - validateDuplicateEmail(request); - } - - private void validateDuplicateEmail(CreateAdminRequest request) { - adminUserRepository.findByEmail(request.email()) - .ifPresent(user -> { - throw DuplicationEmailException.withDetail("email: " + request.email()); - }); - } - @Transactional public void adminPasswordChange(AdminPasswordChangeRequest request, Integer adminId) { Admin admin = adminRepository.getById(adminId); @@ -133,33 +78,14 @@ public void adminPasswordChange(AdminPasswordChangeRequest request, Integer admi @Transactional public AdminLoginResponse adminLogin(AdminLoginRequest request) { User user = adminUserRepository.getByEmail(request.email()); - validateAdminLogin(user, request); + adminUserValidation.validateAdminLogin(user, request); String accessToken = jwtProvider.createToken(user); String refreshToken = String.format("%s-%d", UUID.randomUUID(), user.getId()); - UserToken savedtoken = adminTokenRepository.save(UserToken.create(user.getId(), refreshToken)); + UserToken savedToken = adminTokenRepository.save(UserToken.create(user.getId(), refreshToken)); user.updateLastLoggedTime(LocalDateTime.now()); - return AdminLoginResponse.of(accessToken, savedtoken.getRefreshToken()); - } - - private void validateAdminLogin(User user, AdminLoginRequest request) { - /* 어드민 권한이 없으면 없는 회원으로 간주 */ - if (user.getUserType() != ADMIN) { - throw UserNotFoundException.withDetail("email" + request.email()); - } - - if (adminRepository.findById(user.getId()).isEmpty()) { - throw UserNotFoundException.withDetail("email" + request.email()); - } - - if (!user.isSamePassword(passwordEncoder, request.password())) { - throw new KoinIllegalArgumentException("비밀번호가 틀렸습니다."); - } - - if (!user.isAuthed()) { - throw new AuthorizationException("PL 인증 대기중입니다."); - } + return AdminLoginResponse.of(accessToken, savedToken.getRefreshToken()); } @Transactional @@ -232,145 +158,6 @@ public void updateAdminPermission(AdminPermissionUpdateRequest request, Integer adminRepository.getById(id).updatePermission(request); } - @Transactional - public void allowOwnerPermission(Integer id) { - Owner owner = adminOwnerRepository.getById(id); - owner.getUser().auth(); - Optional ownerShop = adminOwnerShopRedisRepository.findById(id); - if (ownerShop.isPresent()) { - Integer shopId = ownerShop.get().getShopId(); - if (shopId != null) { - Shop shop = adminShopRepository.getById(shopId); - shop.updateOwner(owner); - owner.setGrantShop(true); - } - adminOwnerShopRedisRepository.deleteById(id); - } - } - - public AdminStudentResponse getStudent(Integer userId) { - Student student = adminStudentRepository.getById(userId); - return AdminStudentResponse.from(student); - } - - @Transactional - public AdminStudentUpdateResponse updateStudent(Integer id, AdminStudentUpdateRequest adminRequest) { - Student student = adminStudentRepository.getById(id); - User user = student.getUser(); - validateNicknameDuplication(adminRequest.nickname(), id); - validateDepartmentValid(adminRequest.major()); - user.update(adminRequest.nickname(), adminRequest.name(), - adminRequest.phoneNumber(), UserGender.from(adminRequest.gender())); - user.updateStudentPassword(passwordEncoder, adminRequest.password()); - student.update(adminRequest.studentNumber(), adminRequest.major()); - adminStudentRepository.save(student); - - return AdminStudentUpdateResponse.from(student); - } - - public AdminNewOwnersResponse getNewOwners(OwnersCondition ownersCondition) { - ownersCondition.checkDataConstraintViolation(); - - Integer totalOwners = adminUserRepository.findUsersCountByUserTypeAndIsAuthed(UserType.OWNER, false); - Criteria criteria = Criteria.of(ownersCondition.page(), ownersCondition.limit(), totalOwners); - Sort.Direction direction = ownersCondition.getDirection(); - - Page result = getNewOwnersResultPage(ownersCondition, criteria, direction); - - List ownerIncludingShops = result.getContent().stream() - .map(owner -> { - Optional ownerShop = adminOwnerShopRedisRepository.findById(owner.getId()); - return ownerShop - .map(os -> { - Shop shop = adminShopRepository.findById(os.getShopId()).orElse(null); - return OwnerIncludingShop.of(owner, shop); - }) - .orElseGet(() -> OwnerIncludingShop.of(owner, null)); - }) - .collect(Collectors.toList()); - - return AdminNewOwnersResponse.of(ownerIncludingShops, result, criteria); - } - - public AdminOwnersResponse getOwners(OwnersCondition ownersCondition) { - ownersCondition.checkDataConstraintViolation(); - - Integer totalOwners = adminUserRepository.findUsersCountByUserTypeAndIsAuthed(UserType.OWNER, true); - Criteria criteria = Criteria.of(ownersCondition.page(), ownersCondition.limit(), totalOwners); - Sort.Direction direction = ownersCondition.getDirection(); - - Page result = getOwnersResultPage(ownersCondition, criteria, direction); - - return AdminOwnersResponse.of(result, criteria); - } - - private Page getOwnersResultPage(OwnersCondition ownersCondition, Criteria criteria, - Sort.Direction direction) { - PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), - Sort.by(direction, "user.createdAt")); - - Page result; - - if (ownersCondition.searchType() == OwnersCondition.SearchType.EMAIL) { - result = adminOwnerRepository.findPageOwnersByEmail(ownersCondition.query(), pageRequest); - } else if (ownersCondition.searchType() == OwnersCondition.SearchType.NAME) { - result = adminOwnerRepository.findPageOwnersByName(ownersCondition.query(), pageRequest); - } else { - result = adminOwnerRepository.findPageOwners(pageRequest); - } - - return result; - } - - private Page getNewOwnersResultPage(OwnersCondition ownersCondition, Criteria criteria, - Sort.Direction direction) { - PageRequest pageRequest = PageRequest.of(criteria.getPage(), criteria.getLimit(), - Sort.by(direction, "user.createdAt")); - - Page result; - - if (ownersCondition.searchType() == OwnersCondition.SearchType.EMAIL) { - result = adminOwnerRepository.findPageUnauthenticatedOwnersByEmail(ownersCondition.query(), pageRequest); - } else if (ownersCondition.searchType() == OwnersCondition.SearchType.NAME) { - result = adminOwnerRepository.findPageUnauthenticatedOwnersByName(ownersCondition.query(), pageRequest); - } else { - result = adminOwnerRepository.findPageUnauthenticatedOwners(pageRequest); - } - - return result; - } - - private void validateNicknameDuplication(String nickname, Integer userId) { - if (nickname != null && - adminUserRepository.existsByNicknameAndIdNot(nickname, userId)) { - throw DuplicationNicknameException.withDetail("nickname : " + nickname); - } - } - - private void validateDepartmentValid(String department) { - if (department != null && !StudentDepartment.isValid(department)) { - throw StudentDepartmentNotValidException.withDetail("학부(학과) : " + department); - } - } - - public AdminOwnerResponse getOwner(Integer ownerId) { - Owner owner = adminOwnerRepository.getById(ownerId); - - List shopsId = adminShopRepository.findAllByOwnerId(ownerId) - .stream() - .map(Shop::getId) - .toList(); - - return AdminOwnerResponse.of(owner, shopsId); - } - - @Transactional - public AdminOwnerUpdateResponse updateOwner(Integer ownerId, AdminOwnerUpdateRequest request) { - Owner owner = adminOwnerRepository.getById(ownerId); - owner.update(request); - return AdminOwnerUpdateResponse.from(owner); - } - @Transactional public User getUser(Integer userId) { return adminUserRepository.getById(userId); diff --git a/src/main/java/in/koreatech/koin/admin/user/validation/AdminUserValidation.java b/src/main/java/in/koreatech/koin/admin/user/validation/AdminUserValidation.java new file mode 100644 index 000000000..47fa8214c --- /dev/null +++ b/src/main/java/in/koreatech/koin/admin/user/validation/AdminUserValidation.java @@ -0,0 +1,60 @@ +package in.koreatech.koin.admin.user.validation; + +import static in.koreatech.koin.domain.user.model.UserType.ADMIN; + +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +import in.koreatech.koin.admin.user.dto.AdminLoginRequest; +import in.koreatech.koin.admin.user.repository.AdminRepository; +import in.koreatech.koin.admin.user.repository.AdminUserRepository; +import in.koreatech.koin.domain.user.exception.UserNotFoundException; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.global.auth.exception.AuthorizationException; +import in.koreatech.koin.global.domain.email.exception.DuplicationEmailException; +import in.koreatech.koin.global.domain.email.model.EmailAddress; +import in.koreatech.koin.global.exception.KoinIllegalArgumentException; +import lombok.RequiredArgsConstructor; + +@Component +@RequiredArgsConstructor +public class AdminUserValidation { + + private final PasswordEncoder passwordEncoder; + private final AdminRepository adminRepository; + private final AdminUserRepository adminUserRepository; + + public void validateEmailForAdminCreated(String email) { + EmailAddress emailAddress = EmailAddress.from(email); + emailAddress.validateKoreatechEmail(); + emailAddress.validateAdminEmail(); + + validateDuplicateEmail(email); + } + + private void validateDuplicateEmail(String email) { + adminUserRepository.findByEmail(email) + .ifPresent(user -> { + throw DuplicationEmailException.withDetail("email: " + email); + }); + } + + public void validateAdminLogin(User user, AdminLoginRequest request) { + /* 어드민 권한이 없으면 없는 회원으로 간주 */ + if (user.getUserType() != ADMIN) { + throw UserNotFoundException.withDetail("email" + request.email()); + } + + if (adminRepository.findById(user.getId()).isEmpty()) { + throw UserNotFoundException.withDetail("email" + request.email()); + } + + if (!user.isSamePassword(passwordEncoder, request.password())) { + throw new KoinIllegalArgumentException("비밀번호가 틀렸습니다."); + } + + if (!user.isAuthed()) { + throw new AuthorizationException("PL 인증 대기중입니다."); + } + } +} diff --git a/src/test/java/in/koreatech/koin/admin/acceptance/AdminOwnerApiTest.java b/src/test/java/in/koreatech/koin/admin/acceptance/AdminOwnerApiTest.java new file mode 100644 index 000000000..533857c46 --- /dev/null +++ b/src/test/java/in/koreatech/koin/admin/acceptance/AdminOwnerApiTest.java @@ -0,0 +1,325 @@ +package in.koreatech.koin.admin.acceptance; + +import static in.koreatech.koin.domain.user.model.UserGender.MAN; +import static in.koreatech.koin.domain.user.model.UserType.OWNER; +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +import java.util.ArrayList; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.transaction.annotation.Transactional; + +import in.koreatech.koin.AcceptanceTest; +import in.koreatech.koin.admin.owner.repository.AdminOwnerRepository; +import in.koreatech.koin.admin.owner.repository.AdminOwnerShopRedisRepository; +import in.koreatech.koin.admin.user.model.Admin; +import in.koreatech.koin.domain.owner.model.Owner; +import in.koreatech.koin.domain.owner.model.OwnerAttachment; +import in.koreatech.koin.domain.owner.model.OwnerShop; +import in.koreatech.koin.domain.owner.repository.OwnerShopRedisRepository; +import in.koreatech.koin.domain.shop.model.shop.Shop; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.fixture.ShopFixture; +import in.koreatech.koin.fixture.UserFixture; + +@SuppressWarnings("NonAsciiCharacters") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Transactional +public class AdminOwnerApiTest extends AcceptanceTest { + + @Autowired + private AdminOwnerRepository adminOwnerRepository; + + @Autowired + private AdminOwnerShopRedisRepository adminOwnerShopRedisRepository; + + @Autowired + private OwnerShopRedisRepository ownerShopRedisRepository; + + @Autowired + private UserFixture userFixture; + + @Autowired + private ShopFixture shopFixture; + + @Autowired + private PasswordEncoder passwordEncoder; + + @BeforeAll + void setup() { + clear(); + } + + @Test + void 관리자가_사장님_권한_요청을_허용한다() throws Exception { + Owner owner = userFixture.철수_사장님(); + Shop shop = shopFixture.마슬랜(null); + + Admin adminUser = userFixture.코인_운영자(); + String token = userFixture.getToken(adminUser.getUser()); + + OwnerShop ownerShop = OwnerShop.builder() + .ownerId(owner.getId()) + .shopId(shop.getId()) + .build(); + + ownerShopRedisRepository.save(ownerShop); + + mockMvc.perform( + put("/admin/owner/{id}/authed", owner.getUser().getId()) + .header("Authorization", "Bearer " + token) + ) + .andExpect(status().isOk()); + + //영속성 컨테스트 동기화 + Owner updatedOwner = adminOwnerRepository.getById(owner.getId()); + var resultOwnerShop = adminOwnerShopRedisRepository.findById(owner.getId()); + + assertSoftly( + softly -> { + softly.assertThat(updatedOwner.getUser().isAuthed()).isEqualTo(true); + softly.assertThat(updatedOwner.isGrantShop()).isEqualTo(true); + softly.assertThat(resultOwnerShop).isEmpty(); + } + ); + } + + @Test + void 관리자가_특정_사장을_조회한다() throws Exception { + Owner owner = userFixture.현수_사장님(); + Shop shop = shopFixture.마슬랜(owner); + + Admin adminUser = userFixture.코인_운영자(); + String token = userFixture.getToken(adminUser.getUser()); + + mockMvc.perform( + get("/admin/users/owner/{id}", owner.getUser().getId()) + .header("Authorization", "Bearer " + token) + ) + .andExpect(status().isOk()) + .andExpect(content().json(String.format(""" + { + "id": 1, + "email": "hysoo@naver.com", + "name": "테스트용_현수", + "nickname": "현수", + "company_registration_number": "123-45-67190", + "attachments_url": [ + "https://test.com/현수_사장님_인증사진_1.jpg", + "https://test.com/현수_사장님_인증사진_2.jpg" + ], + "shops_id": [ + %d + ], + "phone_number": "01098765432", + "is_authed": true, + "user_type": "OWNER", + "gender": 0, + "created_at" : "2024-01-15 12:00:00", + "updated_at" : "2024-01-15 12:00:00", + "last_logged_at" : null + } + """, shop.getId()))); + } + + @Test + void 관리자가_특정_사장을_수정한다() throws Exception { + Owner owner = userFixture.현수_사장님(); + Shop shop = shopFixture.마슬랜(owner); + + Admin adminUser = userFixture.코인_운영자(); + String token = userFixture.getToken(adminUser.getUser()); + + mockMvc.perform( + put("/admin/users/owner/{id}", owner.getUser().getId()) + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "company_registration_number" : "123-45-67190", + "grant_shop" : "false", + "grant_event" : "false" + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "company_registration_number" : "123-45-67190", + "email" : "hysoo@naver.com", + "gender" : 0, + "grant_shop" : false, + "grant_event" : false, + "name" : "테스트용_현수", + "nickname" : "현수", + "phone_number" : "01098765432" + } + """)); + } + + @Test + void 관리자가_가입_신청한_사장님_리스트_조회한다() throws Exception { + Owner owner = userFixture.철수_사장님(); + Shop shop = shopFixture.마슬랜(null); + + Admin adminUser = userFixture.코인_운영자(); + String token = userFixture.getToken(adminUser.getUser()); + + OwnerShop ownerShop = OwnerShop.builder() + .ownerId(owner.getId()) + .shopId(shop.getId()) + .build(); + + ownerShopRedisRepository.save(ownerShop); + + mockMvc.perform( + get("/admin/users/new-owners") + .header("Authorization", "Bearer " + token) + .param("searchType", "NAME") + .param("query", "철수") + .param("sort", "CREATED_AT_DESC") + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "total_count": 1, + "current_count": 1, + "total_page": 1, + "current_page": 1, + "owners": [ + { + "id": 1, + "email": "testchulsu@gmail.com", + "name": "테스트용_철수(인증X)", + "phone_number": "01097765112", + "shop_id": 1, + "shop_name": "마슬랜 치킨", + "created_at" : "2024-01-15 12:00:00" + } + ] + } + """)); + } + + @Test + void 관리자가_가입_신청한_사장님_리스트_조회한다_V2() throws Exception { + for (int i = 0; i < 11; i++) { + User user = User.builder() + .password(passwordEncoder.encode("1234")) + .nickname("사장님" + i) + .name("테스트용(인증X)" + i) + .phoneNumber("0109776511" + i) + .userType(OWNER) + .gender(MAN) + .email("testchulsu@gmail.com" + i) + .isAuthed(false) + .isDeleted(false) + .build(); + + Owner owner = Owner.builder() + .user(user) + .companyRegistrationNumber("118-80-567" + i) + .grantShop(true) + .grantEvent(true) + .attachments(new ArrayList<>()) + .build(); + + OwnerAttachment attachment1 = OwnerAttachment.builder() + .url("https://test.com/사장님_인증사진_1" + i + ".jpg") + .isDeleted(false) + .owner(owner) + .build(); + + OwnerAttachment attachment2 = OwnerAttachment.builder() + .url("https://test.com/사장님_인증사진_2" + i + ".jpg") + .isDeleted(false) + .owner(owner) + .build(); + + owner.getAttachments().add(attachment1); + owner.getAttachments().add(attachment2); + + adminOwnerRepository.save(owner); + } + + Admin adminUser = userFixture.코인_운영자(); + String token = userFixture.getToken(adminUser.getUser()); + + mockMvc.perform( + get("/admin/users/new-owners") + .header("Authorization", "Bearer " + token) + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.total_count").value(11)) + .andExpect(jsonPath("$.current_count").value(10)) + .andExpect(jsonPath("$.total_page").value(2)) + .andExpect(jsonPath("$.current_page").value(1)) + .andExpect(jsonPath("$.owners.length()").value(10)); + } + + @Test + void 관리자가_가입_사장님_리스트_조회한다() throws Exception { + for (int i = 0; i < 11; i++) { + User user = User.builder() + .password(passwordEncoder.encode("1234")) + .nickname("사장님" + i) + .name("테스트용(인증X)" + i) + .phoneNumber("0109776511" + i) + .userType(OWNER) + .gender(MAN) + .email("testchulsu@gmail.com" + i) + .isAuthed(true) + .isDeleted(false) + .build(); + + Owner owner = Owner.builder() + .user(user) + .companyRegistrationNumber("118-80-567" + i) + .grantShop(true) + .grantEvent(true) + .attachments(new ArrayList<>()) + .build(); + + OwnerAttachment attachment1 = OwnerAttachment.builder() + .url("https://test.com/사장님_인증사진_1" + i + ".jpg") + .isDeleted(false) + .owner(owner) + .build(); + + OwnerAttachment attachment2 = OwnerAttachment.builder() + .url("https://test.com/사장님_인증사진_2" + i + ".jpg") + .isDeleted(false) + .owner(owner) + .build(); + + owner.getAttachments().add(attachment1); + owner.getAttachments().add(attachment2); + + adminOwnerRepository.save(owner); + } + + Admin adminUser = userFixture.코인_운영자(); + String token = userFixture.getToken(adminUser.getUser()); + + mockMvc.perform( + get("/admin/users/owners") + .header("Authorization", "Bearer " + token) + + ) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.total_count").value(11)) + .andExpect(jsonPath("$.current_count").value(10)) + .andExpect(jsonPath("$.total_page").value(2)) + .andExpect(jsonPath("$.current_page").value(1)) + .andExpect(jsonPath("$.owners.length()").value(10)); + } +} diff --git a/src/test/java/in/koreatech/koin/admin/acceptance/AdminStudentApiTest.java b/src/test/java/in/koreatech/koin/admin/acceptance/AdminStudentApiTest.java new file mode 100644 index 000000000..325d01592 --- /dev/null +++ b/src/test/java/in/koreatech/koin/admin/acceptance/AdminStudentApiTest.java @@ -0,0 +1,270 @@ +package in.koreatech.koin.admin.acceptance; + +import static in.koreatech.koin.domain.user.model.UserGender.MAN; +import static in.koreatech.koin.domain.user.model.UserIdentity.UNDERGRADUATE; +import static in.koreatech.koin.domain.user.model.UserType.STUDENT; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; + +import in.koreatech.koin.AcceptanceTest; +import in.koreatech.koin.admin.student.repository.AdminStudentRepository; +import in.koreatech.koin.admin.user.model.Admin; +import in.koreatech.koin.domain.student.model.Student; +import in.koreatech.koin.domain.user.model.User; +import in.koreatech.koin.domain.user.model.UserGender; +import in.koreatech.koin.fixture.UserFixture; + +@SuppressWarnings("NonAsciiCharacters") +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Transactional +public class AdminStudentApiTest extends AcceptanceTest { + + @Autowired + private AdminStudentRepository adminStudentRepository; + + @Autowired + private TransactionTemplate transactionTemplate; + + @Autowired + private UserFixture userFixture; + + @Autowired + private PasswordEncoder passwordEncoder; + + @BeforeAll + void setup() { + clear(); + } + + @Test + void 관리자가_학생_리스트를_파라미터가_없이_조회한다_페이지네이션() throws Exception { + Admin adminUser = userFixture.코인_운영자(); + + String token = userFixture.getToken(adminUser.getUser()); + + mockMvc.perform( + get("/admin/students") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "current_count": 1, + "current_page": 1, + "students": [ + { + "email": "juno@koreatech.ac.kr", + "id": 1, + "major": "컴퓨터공학부", + "name": "테스트용_준호", + "nickname": "준호", + "student_number": "2019136135" + } + ], + "total_count": 1, + "total_page": 1 + } + """)); + } + + @Test + void 관리자가_학생_리스트를_페이지_수와_limits으로_조회한다_페이지네이션() throws Exception { + for (int i = 0; i < 11; i++) { + Student student = Student.builder() + .studentNumber("2019136135") + .anonymousNickname("익명" + i) + .department("컴퓨터공학부") + .userIdentity(UNDERGRADUATE) + .isGraduated(false) + .user( + User.builder() + .password(passwordEncoder.encode("1234")) + .nickname("성재" + i) + .name("테스트용_성재" + i) + .phoneNumber("01012345670") + .userType(STUDENT) + .gender(MAN) + .email("seongjae@koreatech.ac.kr") + .isAuthed(true) + .isDeleted(false) + .build() + ) + .build(); + + adminStudentRepository.save(student); + } + + Admin adminUser = userFixture.코인_운영자(); + String token = userFixture.getToken(adminUser.getUser()); + + mockMvc.perform( + get("/admin/students") + .header("Authorization", "Bearer " + token) + .param("page", "2") + .param("limit", "10") + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "current_count": 1, + "current_page": 2, + "students": [ + { + "email": "seongjae@koreatech.ac.kr", + "id": 11, + "major": "컴퓨터공학부", + "name": "테스트용_성재10", + "nickname": "성재10", + "student_number": "2019136135" + } + ], + "total_count": 11, + "total_page": 2 + } + """)); + } + + @Test + void 관리자가_학생_리스트를_닉네임으로_조회한다_페이지네이션() throws Exception { + Student student1 = userFixture.성빈_학생(); + Student student2 = userFixture.준호_학생(); + Admin adminUser = userFixture.코인_운영자(); + String token = userFixture.getToken(adminUser.getUser()); + + mockMvc.perform( + get("/admin/students") + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .param("nickname", "준호") + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "current_count": 1, + "current_page": 1, + "students": [ + { + "email": "juno@koreatech.ac.kr", + "id": 2, + "major": "컴퓨터공학부", + "name": "테스트용_준호", + "nickname": "준호", + "student_number": "2019136135" + } + ], + "total_count": 1, + "total_page": 1 + } + """)); + } + + @Test + void 관리자가_특정_학생_정보를_조회한다_관리자가_아니면_403_반환() throws Exception { + Student student = userFixture.준호_학생(); + String token = userFixture.getToken(student.getUser()); + + mockMvc.perform( + get("/admin/users/student/{id}", student.getUser().getId()) + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isForbidden()); + } + + @Test + void 관리자가_특정_학생_정보를_조회한다() throws Exception { + Student student = userFixture.준호_학생(); + + Admin adminUser = userFixture.코인_운영자(); + String token = userFixture.getToken(adminUser.getUser()); + + mockMvc.perform( + get("/admin/users/student/{id}", student.getUser().getId()) + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "anonymous_nickname": "익명", + "created_at": "2024-01-15 12:00:00", + "email": "juno@koreatech.ac.kr", + "gender": 0, + "id": 1, + "is_authed": true, + "is_graduated": false, + "last_logged_at": null, + "major": "컴퓨터공학부", + "name": "테스트용_준호", + "nickname": "준호", + "phone_number": "01012345678", + "student_number": "2019136135", + "updated_at": "2024-01-15 12:00:00", + "user_type": "STUDENT" + } + """)); + } + + @Test + void 관리자가_특정_학생_정보를_수정한다() throws Exception { + Student student = userFixture.준호_학생(); + + Admin adminUser = userFixture.코인_운영자(); + String token = userFixture.getToken(adminUser.getUser()); + + mockMvc.perform( + put("/admin/users/student/{id}", student.getUser().getId()) + .header("Authorization", "Bearer " + token) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "gender" : 1, + "major" : "기계공학부", + "name" : "서정빈", + "password" : "0c4be6acaba1839d3433c1ccf04e1eec4d1fa841ee37cb019addc269e8bc1b77", + "nickname" : "duehee", + "phone_number" : "01023456789", + "student_number" : "2019136136" + } + """) + ) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "anonymous_nickname": "익명", + "email": "juno@koreatech.ac.kr", + "gender": 1, + "major": "기계공학부", + "name": "서정빈", + "nickname": "duehee", + "phone_number": "01023456789", + "student_number": "2019136136" + } + """)); + + transactionTemplate.executeWithoutResult(status -> { + Student result = adminStudentRepository.getById(student.getId()); + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(result.getUser().getName()).isEqualTo("서정빈"); + softly.assertThat(result.getUser().getNickname()).isEqualTo("duehee"); + softly.assertThat(result.getUser().getGender()).isEqualTo(UserGender.from(1)); + softly.assertThat(result.getStudentNumber()).isEqualTo("2019136136"); + } + ); + }); + } +} diff --git a/src/test/java/in/koreatech/koin/admin/acceptance/AdminUserApiTest.java b/src/test/java/in/koreatech/koin/admin/acceptance/AdminUserApiTest.java index f28b46827..869df903b 100644 --- a/src/test/java/in/koreatech/koin/admin/acceptance/AdminUserApiTest.java +++ b/src/test/java/in/koreatech/koin/admin/acceptance/AdminUserApiTest.java @@ -1,45 +1,25 @@ package in.koreatech.koin.admin.acceptance; -import static in.koreatech.koin.domain.user.model.UserGender.MAN; -import static in.koreatech.koin.domain.user.model.UserIdentity.UNDERGRADUATE; -import static in.koreatech.koin.domain.user.model.UserType.OWNER; -import static in.koreatech.koin.domain.user.model.UserType.STUDENT; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.SoftAssertions.assertSoftly; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import java.util.ArrayList; - -import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.web.servlet.MvcResult; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionTemplate; import com.fasterxml.jackson.databind.JsonNode; import in.koreatech.koin.AcceptanceTest; import in.koreatech.koin.admin.user.model.Admin; -import in.koreatech.koin.admin.user.repository.AdminOwnerRepository; -import in.koreatech.koin.admin.user.repository.AdminOwnerShopRedisRepository; import in.koreatech.koin.admin.user.repository.AdminRepository; -import in.koreatech.koin.admin.user.repository.AdminStudentRepository; import in.koreatech.koin.admin.user.repository.AdminUserRepository; -import in.koreatech.koin.domain.owner.model.Owner; -import in.koreatech.koin.domain.owner.model.OwnerAttachment; -import in.koreatech.koin.domain.owner.model.OwnerShop; -import in.koreatech.koin.domain.owner.repository.OwnerShopRedisRepository; -import in.koreatech.koin.domain.shop.model.shop.Shop; import in.koreatech.koin.domain.student.model.Student; import in.koreatech.koin.domain.user.model.User; -import in.koreatech.koin.domain.user.model.UserGender; -import in.koreatech.koin.fixture.ShopFixture; import in.koreatech.koin.fixture.UserFixture; import in.koreatech.koin.support.JsonAssertions; @@ -48,165 +28,20 @@ @Transactional public class AdminUserApiTest extends AcceptanceTest { - @Autowired - private AdminStudentRepository adminStudentRepository; - - @Autowired - private AdminOwnerRepository adminOwnerRepository; - @Autowired private AdminUserRepository adminUserRepository; - @Autowired - private AdminOwnerShopRedisRepository adminOwnerShopRedisRepository; - - @Autowired - private OwnerShopRedisRepository ownerShopRedisRepository; - @Autowired private AdminRepository adminRepository; - @Autowired - private TransactionTemplate transactionTemplate; - @Autowired private UserFixture userFixture; - @Autowired - private ShopFixture shopFixture; - - @Autowired - private PasswordEncoder passwordEncoder; - @BeforeAll void setup() { clear(); } - @Test - void 관리자가_학생_리스트를_파라미터가_없이_조회한다_페이지네이션() throws Exception { - Student student = userFixture.준호_학생(); - Admin adminUser = userFixture.코인_운영자(); - - String token = userFixture.getToken(adminUser.getUser()); - - mockMvc.perform( - get("/admin/students") - .header("Authorization", "Bearer " + token) - .contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isOk()) - .andExpect(content().json(""" - { - "current_count": 1, - "current_page": 1, - "students": [ - { - "email": "juno@koreatech.ac.kr", - "id": 1, - "major": "컴퓨터공학부", - "name": "테스트용_준호", - "nickname": "준호", - "student_number": "2019136135" - } - ], - "total_count": 1, - "total_page": 1 - } - """)); - } - - @Test - void 관리자가_학생_리스트를_페이지_수와_limits으로_조회한다_페이지네이션() throws Exception { - for (int i = 0; i < 11; i++) { - Student student = Student.builder() - .studentNumber("2019136135") - .anonymousNickname("익명" + i) - .department("컴퓨터공학부") - .userIdentity(UNDERGRADUATE) - .isGraduated(false) - .user( - User.builder() - .password(passwordEncoder.encode("1234")) - .nickname("성재" + i) - .name("테스트용_성재" + i) - .phoneNumber("01012345670") - .userType(STUDENT) - .gender(MAN) - .email("seongjae@koreatech.ac.kr") - .isAuthed(true) - .isDeleted(false) - .build() - ) - .build(); - - adminStudentRepository.save(student); - } - - Admin adminUser = userFixture.코인_운영자(); - String token = userFixture.getToken(adminUser.getUser()); - - mockMvc.perform( - get("/admin/students") - .header("Authorization", "Bearer " + token) - .param("page", "2") - .param("limit", "10") - ) - .andExpect(status().isOk()) - .andExpect(content().json(""" - { - "current_count": 1, - "current_page": 2, - "students": [ - { - "email": "seongjae@koreatech.ac.kr", - "id": 11, - "major": "컴퓨터공학부", - "name": "테스트용_성재10", - "nickname": "성재10", - "student_number": "2019136135" - } - ], - "total_count": 11, - "total_page": 2 - } - """)); - } - - @Test - void 관리자가_학생_리스트를_닉네임으로_조회한다_페이지네이션() throws Exception { - Student student1 = userFixture.성빈_학생(); - Student student2 = userFixture.준호_학생(); - Admin adminUser = userFixture.코인_운영자(); - String token = userFixture.getToken(adminUser.getUser()); - - mockMvc.perform( - get("/admin/students") - .header("Authorization", "Bearer " + token) - .contentType(MediaType.APPLICATION_JSON) - .param("nickname", "준호") - ) - .andExpect(status().isOk()) - .andExpect(content().json(""" - { - "current_count": 1, - "current_page": 1, - "students": [ - { - "email": "juno@koreatech.ac.kr", - "id": 2, - "major": "컴퓨터공학부", - "name": "테스트용_준호", - "nickname": "준호", - "student_number": "2019136135" - } - ], - "total_count": 1, - "total_page": 1 - } - """)); - } - @Test void 관리자가_로그인_한다() throws Exception { Admin adminUser = userFixture.코인_운영자(); @@ -313,367 +148,6 @@ void setup() { .andExpect(status().isCreated()); } - @Test - void 관리자가_사장님_권한_요청을_허용한다() throws Exception { - Owner owner = userFixture.철수_사장님(); - Shop shop = shopFixture.마슬랜(null); - - Admin adminUser = userFixture.코인_운영자(); - String token = userFixture.getToken(adminUser.getUser()); - - OwnerShop ownerShop = OwnerShop.builder() - .ownerId(owner.getId()) - .shopId(shop.getId()) - .build(); - - ownerShopRedisRepository.save(ownerShop); - - mockMvc.perform( - put("/admin/owner/{id}/authed", owner.getUser().getId()) - .header("Authorization", "Bearer " + token) - ) - .andExpect(status().isOk()); - - //영속성 컨테스트 동기화 - Owner updatedOwner = adminOwnerRepository.getById(owner.getId()); - var resultOwnerShop = adminOwnerShopRedisRepository.findById(owner.getId()); - - assertSoftly( - softly -> { - softly.assertThat(updatedOwner.getUser().isAuthed()).isEqualTo(true); - softly.assertThat(updatedOwner.isGrantShop()).isEqualTo(true); - softly.assertThat(resultOwnerShop).isEmpty(); - } - ); - } - - @Test - void 관리자가_특정_학생_정보를_조회한다_관리자가_아니면_403_반환() throws Exception { - Student student = userFixture.준호_학생(); - String token = userFixture.getToken(student.getUser()); - - mockMvc.perform( - get("/admin/users/student/{id}", student.getUser().getId()) - .header("Authorization", "Bearer " + token) - .contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isForbidden()); - } - - @Test - void 관리자가_특정_학생_정보를_조회한다() throws Exception { - Student student = userFixture.준호_학생(); - - Admin adminUser = userFixture.코인_운영자(); - String token = userFixture.getToken(adminUser.getUser()); - - mockMvc.perform( - get("/admin/users/student/{id}", student.getUser().getId()) - .header("Authorization", "Bearer " + token) - .contentType(MediaType.APPLICATION_JSON) - ) - .andExpect(status().isOk()) - .andExpect(content().json(""" - { - "anonymous_nickname": "익명", - "created_at": "2024-01-15 12:00:00", - "email": "juno@koreatech.ac.kr", - "gender": 0, - "id": 1, - "is_authed": true, - "is_graduated": false, - "last_logged_at": null, - "major": "컴퓨터공학부", - "name": "테스트용_준호", - "nickname": "준호", - "phone_number": "01012345678", - "student_number": "2019136135", - "updated_at": "2024-01-15 12:00:00", - "user_type": "STUDENT" - } - """)); - } - - @Test - void 관리자가_특정_학생_정보를_수정한다() throws Exception { - Student student = userFixture.준호_학생(); - - Admin adminUser = userFixture.코인_운영자(); - String token = userFixture.getToken(adminUser.getUser()); - - mockMvc.perform( - put("/admin/users/student/{id}", student.getUser().getId()) - .header("Authorization", "Bearer " + token) - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "gender" : 1, - "major" : "기계공학부", - "name" : "서정빈", - "password" : "0c4be6acaba1839d3433c1ccf04e1eec4d1fa841ee37cb019addc269e8bc1b77", - "nickname" : "duehee", - "phone_number" : "01023456789", - "student_number" : "2019136136" - } - """) - ) - .andExpect(status().isOk()) - .andExpect(content().json(""" - { - "anonymous_nickname": "익명", - "email": "juno@koreatech.ac.kr", - "gender": 1, - "major": "기계공학부", - "name": "서정빈", - "nickname": "duehee", - "phone_number": "01023456789", - "student_number": "2019136136" - } - """)); - - transactionTemplate.executeWithoutResult(status -> { - Student result = adminStudentRepository.getById(student.getId()); - SoftAssertions.assertSoftly( - softly -> { - softly.assertThat(result.getUser().getName()).isEqualTo("서정빈"); - softly.assertThat(result.getUser().getNickname()).isEqualTo("duehee"); - softly.assertThat(result.getUser().getGender()).isEqualTo(UserGender.from(1)); - softly.assertThat(result.getStudentNumber()).isEqualTo("2019136136"); - } - ); - }); - } - - @Test - void 관리자가_특정_사장을_조회한다() throws Exception { - Owner owner = userFixture.현수_사장님(); - Shop shop = shopFixture.마슬랜(owner); - - Admin adminUser = userFixture.코인_운영자(); - String token = userFixture.getToken(adminUser.getUser()); - - mockMvc.perform( - get("/admin/users/owner/{id}", owner.getUser().getId()) - .header("Authorization", "Bearer " + token) - ) - .andExpect(status().isOk()) - .andExpect(content().json(String.format(""" - { - "id": 1, - "email": "hysoo@naver.com", - "name": "테스트용_현수", - "nickname": "현수", - "company_registration_number": "123-45-67190", - "attachments_url": [ - "https://test.com/현수_사장님_인증사진_1.jpg", - "https://test.com/현수_사장님_인증사진_2.jpg" - ], - "shops_id": [ - %d - ], - "phone_number": "01098765432", - "is_authed": true, - "user_type": "OWNER", - "gender": 0, - "created_at" : "2024-01-15 12:00:00", - "updated_at" : "2024-01-15 12:00:00", - "last_logged_at" : null - } - """, shop.getId()))); - } - - @Test - void 관리자가_특정_사장을_수정한다() throws Exception { - Owner owner = userFixture.현수_사장님(); - Shop shop = shopFixture.마슬랜(owner); - - Admin adminUser = userFixture.코인_운영자(); - String token = userFixture.getToken(adminUser.getUser()); - - mockMvc.perform( - put("/admin/users/owner/{id}", owner.getUser().getId()) - .header("Authorization", "Bearer " + token) - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { - "company_registration_number" : "123-45-67190", - "grant_shop" : "false", - "grant_event" : "false" - } - """) - ) - .andExpect(status().isOk()) - .andExpect(content().json(""" - { - "company_registration_number" : "123-45-67190", - "email" : "hysoo@naver.com", - "gender" : 0, - "grant_shop" : false, - "grant_event" : false, - "name" : "테스트용_현수", - "nickname" : "현수", - "phone_number" : "01098765432" - } - """)); - } - - @Test - void 관리자가_가입_신청한_사장님_리스트_조회한다() throws Exception { - Owner owner = userFixture.철수_사장님(); - Shop shop = shopFixture.마슬랜(null); - - Admin adminUser = userFixture.코인_운영자(); - String token = userFixture.getToken(adminUser.getUser()); - - OwnerShop ownerShop = OwnerShop.builder() - .ownerId(owner.getId()) - .shopId(shop.getId()) - .build(); - - ownerShopRedisRepository.save(ownerShop); - - mockMvc.perform( - get("/admin/users/new-owners") - .header("Authorization", "Bearer " + token) - .param("searchType", "NAME") - .param("query", "철수") - .param("sort", "CREATED_AT_DESC") - ) - .andExpect(status().isOk()) - .andExpect(content().json(""" - { - "total_count": 1, - "current_count": 1, - "total_page": 1, - "current_page": 1, - "owners": [ - { - "id": 1, - "email": "testchulsu@gmail.com", - "name": "테스트용_철수(인증X)", - "phone_number": "01097765112", - "shop_id": 1, - "shop_name": "마슬랜 치킨", - "created_at" : "2024-01-15 12:00:00" - } - ] - } - """)); - } - - @Test - void 관리자가_가입_신청한_사장님_리스트_조회한다_V2() throws Exception { - for (int i = 0; i < 11; i++) { - User user = User.builder() - .password(passwordEncoder.encode("1234")) - .nickname("사장님" + i) - .name("테스트용(인증X)" + i) - .phoneNumber("0109776511" + i) - .userType(OWNER) - .gender(MAN) - .email("testchulsu@gmail.com" + i) - .isAuthed(false) - .isDeleted(false) - .build(); - - Owner owner = Owner.builder() - .user(user) - .companyRegistrationNumber("118-80-567" + i) - .grantShop(true) - .grantEvent(true) - .attachments(new ArrayList<>()) - .build(); - - OwnerAttachment attachment1 = OwnerAttachment.builder() - .url("https://test.com/사장님_인증사진_1" + i + ".jpg") - .isDeleted(false) - .owner(owner) - .build(); - - OwnerAttachment attachment2 = OwnerAttachment.builder() - .url("https://test.com/사장님_인증사진_2" + i + ".jpg") - .isDeleted(false) - .owner(owner) - .build(); - - owner.getAttachments().add(attachment1); - owner.getAttachments().add(attachment2); - - adminOwnerRepository.save(owner); - } - - Admin adminUser = userFixture.코인_운영자(); - String token = userFixture.getToken(adminUser.getUser()); - - mockMvc.perform( - get("/admin/users/new-owners") - .header("Authorization", "Bearer " + token) - ) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.total_count").value(11)) - .andExpect(jsonPath("$.current_count").value(10)) - .andExpect(jsonPath("$.total_page").value(2)) - .andExpect(jsonPath("$.current_page").value(1)) - .andExpect(jsonPath("$.owners.length()").value(10)); - } - - @Test - void 관리자가_가입_사장님_리스트_조회한다() throws Exception { - for (int i = 0; i < 11; i++) { - User user = User.builder() - .password(passwordEncoder.encode("1234")) - .nickname("사장님" + i) - .name("테스트용(인증X)" + i) - .phoneNumber("0109776511" + i) - .userType(OWNER) - .gender(MAN) - .email("testchulsu@gmail.com" + i) - .isAuthed(true) - .isDeleted(false) - .build(); - - Owner owner = Owner.builder() - .user(user) - .companyRegistrationNumber("118-80-567" + i) - .grantShop(true) - .grantEvent(true) - .attachments(new ArrayList<>()) - .build(); - - OwnerAttachment attachment1 = OwnerAttachment.builder() - .url("https://test.com/사장님_인증사진_1" + i + ".jpg") - .isDeleted(false) - .owner(owner) - .build(); - - OwnerAttachment attachment2 = OwnerAttachment.builder() - .url("https://test.com/사장님_인증사진_2" + i + ".jpg") - .isDeleted(false) - .owner(owner) - .build(); - - owner.getAttachments().add(attachment1); - owner.getAttachments().add(attachment2); - - adminOwnerRepository.save(owner); - } - - Admin adminUser = userFixture.코인_운영자(); - String token = userFixture.getToken(adminUser.getUser()); - - mockMvc.perform( - get("/admin/users/owners") - .header("Authorization", "Bearer " + token) - - ) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.total_count").value(11)) - .andExpect(jsonPath("$.current_count").value(10)) - .andExpect(jsonPath("$.total_page").value(2)) - .andExpect(jsonPath("$.current_page").value(1)) - .andExpect(jsonPath("$.owners.length()").value(10)); - } - @Test void 관리자가_회원을_조회한다() throws Exception { Student student = userFixture.준호_학생();