Skip to content

Commit

Permalink
feat: 회원가입(보호소) 로직을 구현한다. (#140)
Browse files Browse the repository at this point in the history
* feat: 보호소 회원가입 서비스 로직 추가

* refactor: 보호소 전화번호 검증 로직을 개선한다.

* feat: 보호소 주소 검증 로직을 추가한다.

* feat: 보호소 회원가입 api를 구현한다.

* refactor: 상수를 이용해 예외 메시지를 관리하도록 개선한다.

* style: 파일 마지막 개행을 추가한다.
  • Loading branch information
hseong3243 authored Nov 7, 2023
1 parent c356d89 commit b7a1261
Show file tree
Hide file tree
Showing 12 changed files with 431 additions and 106 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.clova.anifriends.domain.shelter.controller;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record RegisterShelterRequest(
@NotBlank(message = "이메일은 필수값입니다.")
String email,
@NotBlank(message = "패스워드는 필수값입니다.")
String password,
@NotBlank(message = "보호소 이름은 필수값입니다.")
String name,
@NotBlank(message = "보호소 주소는 필수값입니다.")
String address,
@NotBlank(message = "보호소 상세 주소는 필수값입니다.")
String addressDetail,
@NotBlank(message = "보호소 전화번호는 필수값입니다.")
String phoneNumber,
@NotBlank(message = "보호소 임시 전화번호는 필수값입니다.")
String sparePhoneNumber,
@NotNull(message = "보호소 주소 공개 여부는 필수값입니다.")
Boolean isOpenedAddress
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
import com.clova.anifriends.domain.shelter.dto.FindShelterDetailResponse;
import com.clova.anifriends.domain.shelter.dto.FindShelterMyPageResponse;
import com.clova.anifriends.domain.shelter.service.ShelterService;
import jakarta.validation.Valid;
import java.net.URI;
import lombok.RequiredArgsConstructor;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -18,6 +22,22 @@ public class ShelterController {

private final ShelterService shelterService;

@PostMapping("/shelters")
public ResponseEntity<Void> registerShelter(
@RequestBody @Valid RegisterShelterRequest registerShelterRequest) {
Long shelterId = shelterService.registerShelter(
registerShelterRequest.email(),
registerShelterRequest.password(),
registerShelterRequest.name(),
registerShelterRequest.address(),
registerShelterRequest.addressDetail(),
registerShelterRequest.phoneNumber(),
registerShelterRequest.sparePhoneNumber(),
registerShelterRequest.isOpenedAddress());
URI location = URI.create("/api/shelters/" + shelterId);
return ResponseEntity.created(location).build();
}

@GetMapping("/volunteers/shelters/{shelterId}/profile")
public ResponseEntity<FindShelterDetailResponse> findShelterDetail(
@PathVariable Long shelterId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

public class ShelterBadRequestException extends BadRequestException {

public ShelterBadRequestException(String message) {
super(ErrorCode.BAD_REQUEST, message);
}

public ShelterBadRequestException(ErrorCode errorCode,
String message) {
super(errorCode, message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,68 @@
import com.clova.anifriends.domain.shelter.Shelter;
import com.clova.anifriends.domain.shelter.dto.FindShelterDetailResponse;
import com.clova.anifriends.domain.shelter.dto.FindShelterMyPageResponse;
import com.clova.anifriends.domain.shelter.exception.ShelterBadRequestException;
import com.clova.anifriends.domain.shelter.exception.ShelterNotFoundException;
import com.clova.anifriends.domain.shelter.repository.ShelterRepository;
import java.text.MessageFormat;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ShelterService {

private static final int MIN_PASSWORD_LENGTH = 6;
private static final int MAX_PASSWORD_LENGTH = 16;

private final ShelterRepository shelterRepository;
private final PasswordEncoder passwordEncoder;

@Transactional
public Long registerShelter(
String email,
String password,
String name,
String address,
String addressDetail,
String phoneNumber,
String sparePhoneNumber,
boolean isOpenedAddress) {
Shelter shelter = new Shelter(
email,
encodePassword(password),
address,
addressDetail,
name,
phoneNumber,
sparePhoneNumber,
isOpenedAddress);
shelterRepository.save(shelter);
return shelter.getShelterId();
}

private String encodePassword(String password) {
validatePasswordNotNull(password);
validatePasswordLength(password);
return passwordEncoder.encode(password);
}

private void validatePasswordNotNull(String password) {
if(Objects.isNull(password)) {
throw new ShelterBadRequestException("패스워드는 필수값입니다.");
}
}

private void validatePasswordLength(String password) {
if(password.length() < MIN_PASSWORD_LENGTH || password.length() > MAX_PASSWORD_LENGTH) {
throw new ShelterBadRequestException(
MessageFormat.format("패스워드는 {0}자 이상, {1}자 이하여야 합니다.",
MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH));
}
}

@Transactional(readOnly = true)
public FindShelterDetailResponse findShelterDetail(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.clova.anifriends.domain.shelter.wrapper;

import com.clova.anifriends.domain.shelter.exception.ShelterBadRequestException;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.text.MessageFormat;
import java.util.Objects;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -11,6 +14,11 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ShelterAddressInfo {

private static final int MIN_ADDRESS_LENGTH = 1;
private static final int MIN_ADDRESS_DETAIL_LENGTH = 1;
private static final int MAX_ADDRESS_LENGTH = 100;
private static final int MAX_ADDRESS_DETAIL_LENGTH = 100;

@Column(name = "address")
private String address;

Expand All @@ -21,8 +29,36 @@ public class ShelterAddressInfo {
private boolean isOpenedAddress;

public ShelterAddressInfo(String address, String addressDetail, boolean isOpenedAddress) {
validateNotNull(address, addressDetail);
validateAddress(address);
validateAddressDetail(addressDetail);
this.address = address;
this.addressDetail = addressDetail;
this.isOpenedAddress = isOpenedAddress;
}

private void validateNotNull(String address, String addressDetail) {
if (Objects.isNull(address)) {
throw new ShelterBadRequestException("보호소 주소는 필수값입니다.");
}
if (Objects.isNull(addressDetail)) {
throw new ShelterBadRequestException("보호소 상세 주소는 필수값입니다.");
}
}

private void validateAddress(String address) {
if(address.isBlank() || address.length() > MAX_ADDRESS_LENGTH) {
throw new ShelterBadRequestException(
MessageFormat.format("보호소 주소는 {0}자 이상, {1}자 이하여야 합니다.",
MIN_ADDRESS_LENGTH, MAX_ADDRESS_LENGTH));
}
}

private void validateAddressDetail(String addressDetail) {
if(addressDetail.isBlank() || addressDetail.length() > MAX_ADDRESS_DETAIL_LENGTH) {
throw new ShelterBadRequestException(
MessageFormat.format("보호소 상세 주소는 {0}자 이상, {1}자 이하여야 합니다.",
MIN_ADDRESS_DETAIL_LENGTH, MAX_ADDRESS_DETAIL_LENGTH));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
import static java.util.Objects.isNull;

import com.clova.anifriends.domain.shelter.exception.ShelterBadRequestException;
import com.clova.anifriends.global.exception.ErrorCode;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.text.MessageFormat;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -16,26 +14,24 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ShelterPassword {

private static final int MIN_PASSWORD_LENGTH = 6;
private static final int MAX_PASSWORD_LENGTH = 16;

@Column(name = "password")
private String password;

public ShelterPassword(String value) {
validateShelterPassword(value);
validateNotNull(value);
validateNotBlank(value);
this.password = value;
}

private void validateShelterPassword(String password) {
if (isNull(password) || password.isBlank()) {
throw new ShelterBadRequestException(ErrorCode.BAD_REQUEST, "비밀번호는 필수 항목입니다.");
private void validateNotNull(String password) {
if (isNull(password)) {
throw new ShelterBadRequestException("비밀번호는 필수 항목입니다.");
}
}

if (password.length() < MIN_PASSWORD_LENGTH || password.length() > MAX_PASSWORD_LENGTH) {
throw new ShelterBadRequestException(ErrorCode.BAD_REQUEST,
MessageFormat.format("이름은 최소 {0}자, 최대 {1}자 입니다.", MIN_PASSWORD_LENGTH,
MAX_PASSWORD_LENGTH));
private void validateNotBlank(String password) {
if (password.isBlank()) {
throw new ShelterBadRequestException("비밀번호는 필수 항목입니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.clova.anifriends.domain.shelter.wrapper;

import static java.util.Objects.isNull;

import com.clova.anifriends.domain.shelter.exception.ShelterBadRequestException;
import com.clova.anifriends.global.exception.ErrorCode;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.util.Objects;
import java.util.regex.Pattern;
import lombok.AccessLevel;
import lombok.Getter;
Expand All @@ -17,7 +16,7 @@
public class ShelterPhoneNumberInfo {

private static final Pattern PHONE_NUMBER_PATTERN = Pattern.compile(
"010-[0-9]{4}-[0-9]{4}|0[1-9]{1,2}-[0-9]{3,4}-[0-9]{4}");
"010-\\d{4}-\\d{4}|0[1-9]{1,2}-\\d{3,4}-\\d{4}");

@Column(name = "phone_number")
private String phoneNumber;
Expand All @@ -26,24 +25,25 @@ public class ShelterPhoneNumberInfo {
private String sparePhoneNumber;

public ShelterPhoneNumberInfo(String phoneNumber, String sparePhoneNumber) {
validatePhoneNumber(phoneNumber, sparePhoneNumber);
validateNotNull(phoneNumber, sparePhoneNumber);
validatePhoneNumberPattern(phoneNumber, sparePhoneNumber);
this.phoneNumber = phoneNumber;
this.sparePhoneNumber = sparePhoneNumber;
}

private void validatePhoneNumber(String phoneNumber, String sparePhoneNumber) {
if (isNull(phoneNumber) || phoneNumber.isBlank()) {
throw new ShelterBadRequestException(ErrorCode.BAD_REQUEST, "전화번호는 필수 항목입니다.");
private void validateNotNull(String phoneNumber, String sparePhoneNumber) {
if(Objects.isNull(phoneNumber)) {
throw new ShelterBadRequestException("전화번호는 필수 항목입니다.");
}
if(Objects.isNull(sparePhoneNumber)) {
throw new ShelterBadRequestException("임시 전화번호는 필수 항목입니다.");
}
}

private void validatePhoneNumberPattern(String phoneNumber, String sparePhoneNumber) {
if (!PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches()) {
throw new ShelterBadRequestException(ErrorCode.BAD_REQUEST, "잘못된 전화번호 입력값입니다.");
}

if (isNull(sparePhoneNumber) || sparePhoneNumber.isBlank()) {
throw new ShelterBadRequestException(ErrorCode.BAD_REQUEST, "임시 전화번호는 필수 항목입니다.");
}

if (!PHONE_NUMBER_PATTERN.matcher(sparePhoneNumber).matches()) {
throw new ShelterBadRequestException(ErrorCode.BAD_REQUEST, "잘못된 임시 전화번호 입력값입니다.");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package com.clova.anifriends.domain.shelter.controller;

import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.clova.anifriends.base.BaseControllerTest;
import com.clova.anifriends.docs.format.DocumentationFormatGenerator;
import com.clova.anifriends.domain.shelter.Shelter;
import com.clova.anifriends.domain.shelter.dto.FindShelterDetailResponse;
import com.clova.anifriends.domain.shelter.dto.FindShelterMyPageResponse;
Expand All @@ -24,6 +30,56 @@

class ShelterControllerTest extends BaseControllerTest {

@Test
@DisplayName("보호소 회원가입 api 호출 시")
void registerShelter() throws Exception {
//given
String email = "[email protected]";
String password = "password123!";
String name = "보호소 이름";
String address = "보호소 주소";
String addressDetail = "보호소 상세 주소";
String phoneNumber = "보호소 전화번호";
String sparePhoneNumber = "보호소 임시 전화번호";
boolean isOpenedAddress = false;
RegisterShelterRequest registerShelterRequest = new RegisterShelterRequest(email, password,
name, address, addressDetail, phoneNumber, sparePhoneNumber, isOpenedAddress);

given(shelterService.registerShelter(anyString(), anyString(), anyString(), anyString(),
anyString(), anyString(), anyString(), anyBoolean()))
.willReturn(1L);

//when
ResultActions resultActions = mockMvc.perform(post("/api/shelters")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(registerShelterRequest)));

//then
resultActions.andExpect(status().isCreated())
.andDo(restDocs.document(
requestFields(
fieldWithPath("email").type(JsonFieldType.STRING).description("보호소 이메일")
.attributes(DocumentationFormatGenerator.getConstraint("@ 포함")),
fieldWithPath("password").type(JsonFieldType.STRING).description("보호소 패스워드")
.attributes(DocumentationFormatGenerator.getConstraint("6자 이상, 16자 이하")),
fieldWithPath("name").type(JsonFieldType.STRING).description("보호소 이름")
.attributes(DocumentationFormatGenerator.getConstraint("1자 이상, 20자 이하")),
fieldWithPath("address").type(JsonFieldType.STRING).description("보호소 주소")
.attributes(DocumentationFormatGenerator.getConstraint("1자 이상, 100자 이하")),
fieldWithPath("addressDetail").type(JsonFieldType.STRING).description("보호소 상세 주소")
.attributes(DocumentationFormatGenerator.getConstraint("1자 이상, 100자 이하")),
fieldWithPath("phoneNumber").type(JsonFieldType.STRING).description("보호소 전화번호")
.attributes(DocumentationFormatGenerator.getConstraint("- 포함, 전화번호 형식 준수")),
fieldWithPath("sparePhoneNumber").type(JsonFieldType.STRING).description("보호소 임시 전화번호")
.attributes(DocumentationFormatGenerator.getConstraint("- 포함, 전화번호 형식 준수")),
fieldWithPath("isOpenedAddress").type(JsonFieldType.BOOLEAN).description("보호소 주소 공개 여부")
),
responseHeaders(
headerWithName("Location").description("생성된 리소스 접근 가능 위치")
)
));
}

@Test
@DisplayName("findShelterDetail 실행 시")
void findShelterDetail() throws Exception {
Expand Down
Loading

0 comments on commit b7a1261

Please sign in to comment.