Skip to content

Commit

Permalink
Merge pull request #120 from woowacourse-teams/feature/#104
Browse files Browse the repository at this point in the history
입력값과 엔티티 필드에 대한 유효성 검증 구현
  • Loading branch information
Mingyum-Kim authored Jul 31, 2024
2 parents b9bb00c + 948a561 commit c85a227
Show file tree
Hide file tree
Showing 14 changed files with 417 additions and 20 deletions.
2 changes: 1 addition & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
.idea

out
14 changes: 10 additions & 4 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,24 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// web
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'

// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

// database
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'

// test
testImplementation 'io.rest-assured:rest-assured:5.3.1'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
package mouda.backend.exception;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception,
HttpHeaders headers, HttpStatusCode status, WebRequest request) {
String error = exception.getBindingResult().getFieldErrors().get(0).getDefaultMessage();

return ResponseEntity.badRequest().body(new ErrorResponse(error));
}

@ExceptionHandler(MoudaException.class)
public ResponseEntity<ErrorResponse> handleMoudaException(MoudaException exception) {
return ResponseEntity.status(exception.getHttpStatus()).body(new ErrorResponse(exception.getMessage()));
Expand Down
16 changes: 16 additions & 0 deletions backend/src/main/java/mouda/backend/member/domain/Member.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package mouda.backend.member.domain;

import org.springframework.http.HttpStatus;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
Expand All @@ -10,12 +12,16 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import mouda.backend.moim.domain.Moim;
import mouda.backend.moim.exception.MoimErrorMessage;
import mouda.backend.moim.exception.MoimException;

@Entity
@Getter
@NoArgsConstructor
public class Member {

private static final int NICKNAME_MAX_LENGTH = 10;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Expand All @@ -27,9 +33,19 @@ public class Member {

@Builder
public Member(String nickname) {
validateNickname(nickname);
this.nickname = nickname;
}

private void validateNickname(String nickname) {
if (nickname.isBlank()) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.MEMBER_NICKNAME_NOT_EXISTS);
}
if (nickname.length() >= NICKNAME_MAX_LENGTH) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.MEMBER_NICKNAME_TOO_LONG);
}
}

public void joinMoim(Moim moim) {
this.moim = moim;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import mouda.backend.common.RestResponse;
import mouda.backend.moim.domain.Moim;
Expand All @@ -27,7 +28,7 @@ public class MoimController implements MoimSwagger {

@Override
@PostMapping
public ResponseEntity<RestResponse<Long>> createMoim(@RequestBody MoimCreateRequest moimCreateRequest) {
public ResponseEntity<RestResponse<Long>> createMoim(@Valid @RequestBody MoimCreateRequest moimCreateRequest) {
Moim moim = moimService.createMoim(moimCreateRequest);

return ResponseEntity.ok().body(new RestResponse<>(moim.getId()));
Expand All @@ -43,7 +44,7 @@ public ResponseEntity<RestResponse<MoimFindAllResponses>> findAllMoim() {

@Override
@GetMapping("/{moimId}")
public ResponseEntity<RestResponse<MoimDetailsFindResponse>> findMoimDetails(@PathVariable Long moimId) {
public ResponseEntity<RestResponse<MoimDetailsFindResponse>> findMoimDetails(@PathVariable("moimId") Long moimId) {
MoimDetailsFindResponse moimDetailsFindResponse = moimService.findMoimDetails(moimId);

return ResponseEntity.ok().body(new RestResponse<>(moimDetailsFindResponse));
Expand Down
74 changes: 74 additions & 0 deletions backend/src/main/java/mouda/backend/moim/domain/Moim.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package mouda.backend.moim.domain;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

import org.springframework.http.HttpStatus;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
Expand All @@ -22,18 +24,30 @@
@NoArgsConstructor
public class Moim {

private static final int TITLE_MAX_LENGTH = 30;
private static final int PLACE_MAX_LENGTH = 100;
private static final int MAX_PEOPLE_LOWER_BOUND = 1;
private static final int MAX_PEOPLE_UPPER_BOUND = 99;
private static final int AUTHOR_NICKNAME_MAX_LENGTH = 10;
private static final int DESCRIPTION_MAX_LENGTH = 1000;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String title;

@Column(nullable = false)
private LocalDate date;

@Column(nullable = false)
private LocalTime time;

@Column(nullable = false)
private String place;

@Column(nullable = false)
private int maxPeople;

private String description;
Expand All @@ -52,6 +66,14 @@ public Moim(
int maxPeople,
String description
) {
validateTitle(title);
validateDate(date);
validateTime(time);
validateMoimIsFuture(date, time);
validatePlace(place);
validateMaxPeople(maxPeople);
validateDescription(description);

this.title = title;
this.date = date;
this.time = time;
Expand All @@ -60,6 +82,58 @@ public Moim(
this.description = description;
}

private void validateTitle(String title) {
if (title.isBlank()) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.TITLE_NOT_EXIST);
}
if (title.length() > TITLE_MAX_LENGTH) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.TITLE_TOO_LONG);
}
}

private void validateDate(LocalDate date) {
if (date == null) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.DATE_NOT_EXIST);
}
}

private void validateTime(LocalTime time) {
if (time == null) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.TIME_NOT_EXIST);
}
}

private void validateMoimIsFuture(LocalDate date, LocalTime time) {
LocalDateTime moimDateTime = LocalDateTime.of(date, time);
if (moimDateTime.isBefore(LocalDateTime.now())) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.PAST_DATE_TIME);
}
}

private void validatePlace(String place) {
if (place.isBlank()) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.PLACE_NOT_EXIST);
}
if (place.length() > PLACE_MAX_LENGTH) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.PLACE_TOO_LONG);
}
}

private void validateMaxPeople(int maxPeople) {
if (maxPeople < MAX_PEOPLE_LOWER_BOUND) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.MAX_PEOPLE_IS_POSITIVE);
}
if (maxPeople > MAX_PEOPLE_UPPER_BOUND) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.MAX_PEOPLE_TOO_MANY);
}
}

private void validateDescription(String description) {
if (description != null && description.length() > DESCRIPTION_MAX_LENGTH) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.DESCRIPTION_TOO_LONG);
}
}

public void validateAlreadyFullMoim(int currentPeople) {
if (currentPeople > maxPeople) {
throw new MoimException(HttpStatus.BAD_REQUEST, MoimErrorMessage.MAX_PEOPLE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,29 @@
import java.time.LocalDate;
import java.time.LocalTime;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import mouda.backend.moim.domain.Moim;

public record MoimCreateRequest(
@NotBlank
String title,

@NotNull
LocalDate date,

@NotNull
LocalTime time,

@NotBlank
String place,

@NotNull
Integer maxPeople,

@NotBlank
String authorNickname,

String description
) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
package mouda.backend.moim.dto.request;

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

public record MoimJoinRequest(
@NotNull
@Positive
Long moimId,

@NotBlank
String nickname
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,20 @@ public enum MoimErrorMessage {

NOT_FOUND("모임이 존재하지 않습니다."),
MAX_PEOPLE("모임 최대 인원 수를 초과합니다."),
;
PAST_DATE_TIME("모임 날짜를 현재 시점 이후로 입력해주세요."),
TITLE_NOT_EXIST("모임 제목을 입력해주세요."),
TITLE_TOO_LONG("모임 제목을 조금 더 짧게 입력해주세요."),
DATE_NOT_EXIST("모임 날짜를 입력해주세요."),
TIME_NOT_EXIST("모임 시간을 입력해주세요."),
PLACE_NOT_EXIST("모임 장소를 입력해주세요."),
PLACE_TOO_LONG("모임 장소를 조금 더 짧게 입력해주세요."),
MAX_PEOPLE_IS_POSITIVE("모임 최대 인원은 양수여야 합니다."),
MAX_PEOPLE_TOO_MANY("모임 최대 인원을 조금 더 적게 입력해주세요."),
AUTHOR_NICKNAME_NOT_EXIST("모임 생성자 닉네임을 입력해주세요."),
AUTHOR_NICKNAME_TOO_LONG("모임 생성자 이름을 조금 더 짧게 입력해주세요."),
DESCRIPTION_TOO_LONG("모임 설명을 조금 더 짧게 입력해주세요."),
MEMBER_NICKNAME_NOT_EXISTS("모임 참여자 닉네임을 입력해주세요."),
MEMBER_NICKNAME_TOO_LONG("모임 참여자 닉네임을 조금 더 짧게 입력해주세요.");

private final String message;
}
Loading

0 comments on commit c85a227

Please sign in to comment.