Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: 케이크샵 인증 요청 리스트 API 개발 #183

Merged
merged 16 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
783b2e6
Fix | CAKK-25 | Test method 접근제한자 수정
YongsHub Aug 13, 2024
339ea52
Test | CAKK-25 | 케이크 샵 주인 인증 완료 시, 정책 도메인으로 캡슐화 및 테스트
YongsHub Aug 13, 2024
390df7c
Feature | CAKK-25 | 인증 상태 여부 필드를 위한 Converter 추가
YongsHub Aug 13, 2024
551d0ef
Fix | CAKK-25 | 메서드 네이밍 변경
YongsHub Aug 13, 2024
63c3262
Test | CAKK-25 | 인증 정책 테스트 추가
YongsHub Aug 13, 2024
a3da8d8
Refactor | CAKK-25 | 사업자 정보 케이크샵 주인 업데이트 로직 리팩토링
YongsHub Aug 13, 2024
d10b240
Feature | CAKK-25 | 케이크샵 주인 후보 여부 조회 로직 추가 및 테스트
YongsHub Aug 13, 2024
98fd075
Fix | CAKK-25 | 케이크샵 생성자 롬복 제거
YongsHub Aug 13, 2024
ee81b04
Fix | CAKK-25 | 어드민 API 분리
YongsHub Aug 13, 2024
a8ae1cb
Feature | CAKK-25 | 사장님 인증 요청 리스트 목록 어플리케이션 로직 추가
YongsHub Aug 13, 2024
17b8baf
Test | CAKK-25| 사장님 인증 요청 목록 통합 테스트 추가
YongsHub Aug 13, 2024
4407ae9
Refactor | CAKK-25 | checkstyle 반영
YongsHub Aug 13, 2024
c6f6206
Test | CAKK-25 | 빠졌던 테스트 추가 및 build 문제 해결
YongsHub Aug 13, 2024
f329b29
Fix | CAKK-25 | 인증 정책 응집도를 위한 패키지 수정
YongsHub Aug 13, 2024
b740880
Fix | CAKK-25 | 요청 데이터에 따른 response 수정
YongsHub Aug 13, 2024
964166a
Fix | CAKK-25 | 코드 리뷰 반영
YongsHub Aug 14, 2024
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
@@ -0,0 +1,53 @@
package com.cakk.api.controller.shop;

import jakarta.validation.Valid;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;

import com.cakk.api.dto.request.shop.CreateShopRequest;
import com.cakk.api.dto.request.shop.PromotionRequest;
import com.cakk.api.dto.response.shop.CakeShopCreateResponse;
import com.cakk.api.dto.response.shop.CakeShopOwnerCandidateResponse;
import com.cakk.api.service.shop.ShopService;
import com.cakk.common.response.ApiResponse;

@RestController
@RequiredArgsConstructor
@RequestMapping("/admin")
public class AdminController {

private final ShopService shopService;

@GetMapping("/shops/candidates")
public ApiResponse<CakeShopOwnerCandidateResponse> getBusinessOwnerCandidates() {
final CakeShopOwnerCandidateResponse response = shopService.getBusinessOwnerCandidates();

return ApiResponse.success(response);
}

@PostMapping("/shops/create")
public ApiResponse<CakeShopCreateResponse> createByAdmin(
@Valid @RequestBody CreateShopRequest createShopRequest
) {
final CakeShopCreateResponse response = shopService.createCakeShopByCertification(createShopRequest);

return ApiResponse.success(response);
}

@PutMapping("/shops/promote")
public ApiResponse<Void> promoteUser(
@Valid @RequestBody PromotionRequest promotionRequest
) {
shopService.promoteUserToBusinessOwner(promotionRequest);
return ApiResponse.success();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
Expand All @@ -20,16 +19,13 @@
import com.cakk.api.dto.request.operation.UpdateShopOperationRequest;
import com.cakk.api.dto.request.shop.CakeShopSearchByViewsRequest;
import com.cakk.api.dto.request.shop.CakeShopSearchRequest;
import com.cakk.api.dto.request.shop.CreateShopRequest;
import com.cakk.api.dto.request.shop.PromotionRequest;
import com.cakk.api.dto.request.shop.SearchShopByLocationRequest;
import com.cakk.api.dto.request.shop.UpdateShopAddressRequest;
import com.cakk.api.dto.request.shop.UpdateShopRequest;
import com.cakk.api.dto.request.user.CertificationRequest;
import com.cakk.api.dto.response.like.HeartResponse;
import com.cakk.api.dto.response.shop.CakeShopByMapResponse;
import com.cakk.api.dto.response.shop.CakeShopByMineResponse;
import com.cakk.api.dto.response.shop.CakeShopCreateResponse;
import com.cakk.api.dto.response.shop.CakeShopDetailResponse;
import com.cakk.api.dto.response.shop.CakeShopInfoResponse;
import com.cakk.api.dto.response.shop.CakeShopOwnerResponse;
Expand Down Expand Up @@ -60,23 +56,6 @@ public ApiResponse<Void> requestCertification(
return ApiResponse.success();
}

@PostMapping("/admin/create")
public ApiResponse<CakeShopCreateResponse> createByAdmin(
@Valid @RequestBody CreateShopRequest createShopRequest
) {
final CakeShopCreateResponse response = shopService.createCakeShopByCertification(createShopRequest);

return ApiResponse.success(response);
}

@PatchMapping("/admin/promote")
public ApiResponse<Void> promoteUser(
@Valid @RequestBody PromotionRequest promotionRequest
) {
shopService.promoteUserToBusinessOwner(promotionRequest);
return ApiResponse.success();
}

@GetMapping("/{cakeShopId}/simple")
public ApiResponse<CakeShopSimpleResponse> simple(
@PathVariable Long cakeShopId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.cakk.api.dto.param.operation;

import java.time.LocalDateTime;

import lombok.Builder;

@Builder
public record OwnerCandidateParam(
Long userId,
String nickname,
String profileImageUrl,
String email,
LocalDateTime timestamp
) {
}
YongsHub marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.cakk.api.dto.response.shop;

import java.util.List;

import com.cakk.api.dto.param.operation.OwnerCandidateParam;

public record CakeShopOwnerCandidateResponse(
List<OwnerCandidateParam> candidates
) {
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.cakk.api.mapper;

import java.util.List;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;

import com.cakk.api.dto.param.operation.OwnerCandidateParam;
import com.cakk.api.dto.response.shop.CakeShopOwnerCandidateResponse;
import com.cakk.domain.mysql.entity.user.BusinessInformation;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BusinessInformationMapper {

public static CakeShopOwnerCandidateResponse supplyCakeShopOwnerCandidateResponseBy(
List<BusinessInformation> businessInformations
) {
List<OwnerCandidateParam> candidates = businessInformations
.stream()
.map(businessInformation ->
OwnerCandidateParam.builder()
.userId(businessInformation.getUser().getId())
.nickname(businessInformation.getUser().getNickname())
.profileImageUrl(businessInformation.getUser().getProfileImageUrl())
.email(businessInformation.getUser().getEmail())
.timestamp(businessInformation.getUpdatedAt())
.build())
.toList();

return new CakeShopOwnerCandidateResponse(candidates);
}
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.cakk.api.mapper;

import java.util.List;
import java.util.stream.Collectors;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
Expand Down
19 changes: 17 additions & 2 deletions cakk-api/src/main/java/com/cakk/api/service/shop/ShopService.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@
import com.cakk.api.dto.response.shop.CakeShopCreateResponse;
import com.cakk.api.dto.response.shop.CakeShopDetailResponse;
import com.cakk.api.dto.response.shop.CakeShopInfoResponse;
import com.cakk.api.dto.response.shop.CakeShopOwnerCandidateResponse;
import com.cakk.api.dto.response.shop.CakeShopOwnerResponse;
import com.cakk.api.dto.response.shop.CakeShopSearchResponse;
import com.cakk.api.dto.response.shop.CakeShopSimpleResponse;
import com.cakk.api.mapper.BusinessInformationMapper;
import com.cakk.api.mapper.LinkMapper;
import com.cakk.api.mapper.PointMapper;
import com.cakk.api.mapper.ShopMapper;
import com.cakk.domain.mysql.bo.CakeShops;
import com.cakk.domain.mysql.bo.user.VerificationPolicy;
import com.cakk.domain.mysql.dto.param.link.UpdateLinkParam;
import com.cakk.domain.mysql.dto.param.operation.UpdateShopOperationParam;
import com.cakk.domain.mysql.dto.param.shop.CakeShopByLocationParam;
Expand Down Expand Up @@ -63,7 +66,7 @@ public class ShopService {
private final BusinessInformationReader businessInformationReader;
private final CakeShopWriter cakeShopWriter;
private final CakeShopViewsRedisRepository cakeShopViewsRedisRepository;

private final VerificationPolicy verificationPolicy;
private final ApplicationEventPublisher publisher;

@Transactional
Expand All @@ -83,7 +86,7 @@ public void promoteUserToBusinessOwner(final PromotionRequest request) {
final User user = userReader.findByUserId(request.userId());
final BusinessInformation businessInformation = cakeShopReader.findBusinessInformationWithShop(request.cakeShopId());

businessInformation.promotedByBusinessOwner(user);
businessInformation.updateBusinessOwner(verificationPolicy, user);
}

@Transactional
Expand Down Expand Up @@ -205,4 +208,16 @@ public CakeShopOwnerResponse isExistBusinessInformation(final User owner, final

return ShopMapper.supplyCakeShopOwnerResponseBy(isOwned);
}

@Transactional(readOnly = true)
public CakeShopOwnerCandidateResponse getBusinessOwnerCandidates() {
List<BusinessInformation> businessInformations = businessInformationReader.findAllCakeShopBusinessOwnerCandidates();

businessInformations = businessInformations
.stream()
.filter(businessInformation -> businessInformation.isBusinessOwnerCandidate(verificationPolicy))
.toList();

return BusinessInformationMapper.supplyCakeShopOwnerCandidateResponseBy(businessInformations);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.cakk.api.integration.admin;

import static org.junit.Assert.*;
import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.*;

import org.assertj.core.api.Assertions;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlGroup;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import com.cakk.api.common.annotation.TestWithDisplayName;
import com.cakk.api.common.base.IntegrationTest;
import com.cakk.api.dto.request.shop.CreateShopRequest;
import com.cakk.api.dto.request.shop.PromotionRequest;
import com.cakk.api.dto.response.shop.CakeShopCreateResponse;
import com.cakk.api.dto.response.shop.CakeShopOwnerCandidateResponse;
import com.cakk.common.enums.ReturnCode;
import com.cakk.common.response.ApiResponse;

@SqlGroup({
@Sql(scripts = {
"/sql/insert-test-user.sql",
"/sql/insert-business-information.sql"
}, executionPhase = BEFORE_TEST_METHOD),
@Sql(scripts = "/sql/delete-all.sql", executionPhase = AFTER_TEST_METHOD)
})
public class AdminIntegrationTest extends IntegrationTest {

private static final String API_URL = "/api/v1/admin";


@TestWithDisplayName("백 오피스 API, 케이크샵 생성에 성공한다")
void backOfficeCreateCakeShop() {
final String url = "%s%d%s".formatted(BASE_URL, port, API_URL);
final UriComponents uriComponents = UriComponentsBuilder
.fromUriString(url)
.path("/shops/create")
.build();
final CreateShopRequest request = getConstructorMonkey().giveMeBuilder(CreateShopRequest.class)
.sample();

// when
final ResponseEntity<ApiResponse> responseEntity = restTemplate.exchange(
uriComponents.toUriString(),
HttpMethod.POST,
new HttpEntity<>(request),
ApiResponse.class);

// then
final ApiResponse response = objectMapper.convertValue(responseEntity.getBody(), ApiResponse.class);
final CakeShopCreateResponse data = objectMapper.convertValue(response.getData(), CakeShopCreateResponse.class);

Assertions.assertThat(data.cakeShopId()).isNotNull();
}

@TestWithDisplayName("백 오피스 API, 케이크샵 사장님 인증 요청 리스트 조회에 성공한다")
void backOfficeSearchByCakeShopBusinessOwnerCandidates() {
//given
final String url = "%s%d%s".formatted(BASE_URL, port, API_URL);
final UriComponents uriComponents = UriComponentsBuilder
.fromUriString(url)
.path("/shops/candidates")
.build();

// when
final ResponseEntity<ApiResponse> responseEntity = restTemplate.getForEntity(uriComponents.toUriString(), ApiResponse.class);

// then
final ApiResponse response = objectMapper.convertValue(responseEntity.getBody(), ApiResponse.class);
final CakeShopOwnerCandidateResponse data = objectMapper.convertValue(response.getData(),
CakeShopOwnerCandidateResponse.class);

assertEquals(HttpStatusCode.valueOf(200), responseEntity.getStatusCode());
assertEquals(ReturnCode.SUCCESS.getCode(), response.getReturnCode());
assertEquals(ReturnCode.SUCCESS.getMessage(), response.getReturnMessage());
}

@TestWithDisplayName("백 오피스 API, 케이크 샵 사장님 인증 완료 처리에 성공한다")
void backOfficeCakeShopBusinessOwnerApproved() {
//given
final String url = "%s%d%s".formatted(BASE_URL, port, API_URL);
final UriComponents uriComponents = UriComponentsBuilder
.fromUriString(url)
.path("/shops/promote")
.build();

final PromotionRequest request = getConstructorMonkey().giveMeBuilder(PromotionRequest.class)
.set("userId", 1L)
.set("cakeShopId", 1L)
.sample();

// when
final ResponseEntity<ApiResponse> responseEntity = restTemplate.exchange(
uriComponents.toUriString(),
HttpMethod.PUT,
new HttpEntity<>(request),
ApiResponse.class);

// then
final ApiResponse response = objectMapper.convertValue(responseEntity.getBody(), ApiResponse.class);

assertEquals(HttpStatusCode.valueOf(200), responseEntity.getStatusCode());
assertEquals(ReturnCode.SUCCESS.getCode(), response.getReturnCode());
assertEquals(ReturnCode.SUCCESS.getMessage(), response.getReturnMessage());
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -107,30 +107,6 @@ void setUp() {
cakeShopViewsRedisRepository.clear();
}

@TestWithDisplayName("백 오피스 API, 케이크샵 생성에 성공한다")
void backOfficeCreateCakeShop() {
final String url = "%s%d%s".formatted(BASE_URL, port, API_URL);
final UriComponents uriComponents = UriComponentsBuilder
.fromUriString(url)
.path("/admin/create")
.build();
final CreateShopRequest request = getConstructorMonkey().giveMeBuilder(CreateShopRequest.class)
.sample();

// when
final ResponseEntity<ApiResponse> responseEntity = restTemplate.exchange(
uriComponents.toUriString(),
HttpMethod.POST,
new HttpEntity<>(request),
ApiResponse.class);

// then
final ApiResponse response = objectMapper.convertValue(responseEntity.getBody(), ApiResponse.class);
final CakeShopCreateResponse data = objectMapper.convertValue(response.getData(), CakeShopCreateResponse.class);

Assertions.assertThat(data.cakeShopId()).isNotNull();
}

@TestWithDisplayName("케이크 샵을 간단 조회에 성공한다.")
void simple1() {
final String url = "%s%d%s".formatted(BASE_URL, port, API_URL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.cakk.api.mapper.ShopMapper;
import com.cakk.common.enums.ReturnCode;
import com.cakk.common.exception.CakkException;
import com.cakk.domain.mysql.bo.user.VerificationPolicy;
import com.cakk.domain.mysql.dto.param.shop.CakeShopDetailParam;
import com.cakk.domain.mysql.dto.param.shop.CakeShopInfoParam;
import com.cakk.domain.mysql.dto.param.shop.CakeShopSimpleParam;
Expand Down Expand Up @@ -64,6 +65,9 @@ public class ShopServiceTest extends ServiceTest {
@Mock
private ApplicationEventPublisher publisher;

@Mock
private VerificationPolicy verificationPolicy;

private CreateShopRequest getCreateShopRequestFixture() {
return getConstructorMonkey().giveMeBuilder(CreateShopRequest.class)
.set("businessNumber", Arbitraries.strings().withCharRange('a', 'z').ofMinLength(1).ofMaxLength(7))
Expand Down
Loading
Loading