Skip to content

Commit

Permalink
Merge pull request #25 from Team-BomBomBom/feat/16-create-study
Browse files Browse the repository at this point in the history
Feat: #16 μŠ€ν„°λ”” 생성 API κ΅¬ν˜„
  • Loading branch information
msjang4 authored Jun 25, 2024
2 parents 58cc63c + 2b46baf commit dedd74b
Show file tree
Hide file tree
Showing 21 changed files with 1,998 additions and 238 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bombombom.devs.global.exception;

import java.time.LocalDateTime;
import java.util.Map;

public record DetailedErrorResponse(LocalDateTime timestamp, int status, String message,
Map<String, String> errorDetails) {

public DetailedErrorResponse(int status, String message, Map<String, String> errorDetails) {
this(LocalDateTime.now(), status, message, errorDetails);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package com.bombombom.devs.global.exception;

import com.bombombom.devs.user.exception.ExistUsernameException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
Expand All @@ -23,13 +27,19 @@ protected ResponseEntity<ErrorResponse> handleInvalidData(RuntimeException e) {
}

@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleInvalidDtoField(
MethodArgumentNotValidException e) {
FieldError fieldError = e.getFieldErrors().get(0);
ErrorResponse errorResponse = new ErrorResponse(
protected ResponseEntity<DetailedErrorResponse> handleInvalidDtoField(
MethodArgumentNotValidException e) throws JsonProcessingException {

Map<String, String> errorDetails = new HashMap<>();

e.getFieldErrors()
.forEach(action -> errorDetails.put(action.getField(), action.getDefaultMessage()));

DetailedErrorResponse detailedErrorResponse = new DetailedErrorResponse(
HttpStatus.BAD_REQUEST.value(),
fieldError.getDefaultMessage()
e.getFieldErrors().getFirst().getDefaultMessage(),
errorDetails
);
return ResponseEntity.badRequest().body(errorResponse);
return ResponseEntity.badRequest().body(detailedErrorResponse);
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/bombombom/devs/study/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.bombombom.devs.study;

public class Constants {

public static final int MAX_CAPACITY = 20;
public static final int MAX_WEEKS = 52;

public static final int MAX_RELIABLITY_LIMIT = 100;
public static final int MAX_PENALTY = 100_000;
public static final int MAX_DIFFICULTY_LEVEL = 29;
public static final int MAX_PROBLEM_COUNT = 20;

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,26 @@
import com.bombombom.devs.study.controller.dto.request.JoinStudyRequest;
import com.bombombom.devs.study.controller.dto.request.RegisterAlgorithmStudyRequest;
import com.bombombom.devs.study.controller.dto.request.RegisterBookStudyRequest;
import com.bombombom.devs.study.controller.dto.response.AlgorithmStudyResponse;
import com.bombombom.devs.study.controller.dto.response.BookStudyResponse;
import com.bombombom.devs.study.controller.dto.response.StudyPageResponse;
import com.bombombom.devs.study.controller.dto.response.StudyResponse;
import com.bombombom.devs.study.service.StudyService;
import com.bombombom.devs.global.security.AppUserDetails;
import com.bombombom.devs.study.service.dto.command.JoinStudyCommand;
import com.bombombom.devs.study.service.dto.result.AlgorithmStudyResult;
import com.bombombom.devs.study.service.dto.result.BookStudyResult;
import com.bombombom.devs.study.service.dto.result.StudyResult;
import jakarta.validation.Valid;
import java.net.URI;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -31,25 +40,50 @@ public class StudyController {
private final StudyService studyService;

@PostMapping("/algo")
public ResponseEntity<Void> registerAlgorithmStudy(
public ResponseEntity<AlgorithmStudyResponse> registerAlgorithmStudy(
@LoginUser AppUserDetails userDetails,
@Valid @RequestBody RegisterAlgorithmStudyRequest registerAlgorithmStudyRequest) {
log.info("{}", registerAlgorithmStudyRequest);
Long id = studyService.createAlgorithmStudy(registerAlgorithmStudyRequest.toServiceDto());
return ResponseEntity.created(URI.create(RESOURCE_PATH + "/" + id)).build();

AlgorithmStudyResult algorithmStudyResult = studyService.createAlgorithmStudy(
userDetails.getId(),
registerAlgorithmStudyRequest.toServiceDto());

AlgorithmStudyResponse algorithmStudyResponse = AlgorithmStudyResponse.fromResult(
algorithmStudyResult);

return ResponseEntity.created(URI.create(RESOURCE_PATH + "/" + algorithmStudyResponse.id()))
.body(algorithmStudyResponse);
}

@PostMapping("/book")
public ResponseEntity<Void> registerBookStudy(
public ResponseEntity<StudyResponse> registerBookStudy(
@LoginUser AppUserDetails userDetails,
@Valid @RequestBody RegisterBookStudyRequest registerBookStudyRequest) {
log.info("{}", registerBookStudyRequest);
Long id = studyService.createBookStudy(registerBookStudyRequest.toServiceDto());
return ResponseEntity.created(URI.create(RESOURCE_PATH + id)).build();

BookStudyResult bookStudyResult = studyService.createBookStudy(
userDetails.getId(),
registerBookStudyRequest.toServiceDto());

BookStudyResponse bookStudyResponse = BookStudyResponse.fromResult(bookStudyResult);

return ResponseEntity.created(URI.create(RESOURCE_PATH + "/" + bookStudyResponse.id()))
.body(bookStudyResponse);
}

@GetMapping
public ResponseEntity<StudyPageResponse> studyList(
@PageableDefault(sort = "id", direction = Sort.Direction.DESC) Pageable pageable) {
StudyPageResponse studyPageResponse = studyService.readStudy(pageable);

Page<StudyResponse> studyPage = studyService.readStudy(pageable)
.map(StudyResponse::fromResult);

StudyPageResponse studyPageResponse = StudyPageResponse.builder()
.contents(studyPage.getContent())
.pageNumber(studyPage.getNumber())
.totalPages(studyPage.getTotalPages())
.totalElements(studyPage.getTotalElements())
.build();

return ResponseEntity.ok(studyPageResponse);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
package com.bombombom.devs.study.controller.dto.request;


import static com.bombombom.devs.study.Constants.MAX_CAPACITY;
import static com.bombombom.devs.study.Constants.MAX_DIFFICULTY_LEVEL;
import static com.bombombom.devs.study.Constants.MAX_PENALTY;
import static com.bombombom.devs.study.Constants.MAX_PROBLEM_COUNT;
import static com.bombombom.devs.study.Constants.MAX_RELIABLITY_LIMIT;
import static com.bombombom.devs.study.Constants.MAX_WEEKS;

import com.bombombom.devs.study.models.AlgorithmStudy;
import com.bombombom.devs.study.models.StudyStatus;
import com.bombombom.devs.study.models.StudyType;
import com.bombombom.devs.study.service.dto.command.RegisterAlgorithmStudyCommand;
import jakarta.persistence.criteria.CriteriaBuilder.In;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import lombok.Builder;
import org.hibernate.validator.constraints.Range;

@Builder
public record RegisterAlgorithmStudyRequest(
@NotNull String name,
@NotNull String introduce,

Integer capacity,
Integer weeks,
@NotBlank @Size(max = 255, message = "μŠ€ν„°λ””λͺ…은 255자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€.") String name,
@NotBlank @Size(max = 500, message = "μŠ€ν„°λ””μ†Œκ°œλŠ” 500자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€.") String introduce,
@NotNull @Range(min = 1, max = MAX_CAPACITY) Integer capacity,
@NotNull @Range(min = 1, max = MAX_WEEKS) Integer weeks,
@NotNull LocalDate startDate,
Integer reliabilityLimit,
Integer penalty,
Integer difficultyBegin,
Integer difficultyEnd,
Integer problemCount
@NotNull @Range(max = MAX_RELIABLITY_LIMIT) Integer reliabilityLimit,
@NotNull @Range(max = MAX_PENALTY) Integer penalty,
@NotNull @Range(max = MAX_DIFFICULTY_LEVEL) Integer difficultyBegin,
@NotNull @Range(max = MAX_DIFFICULTY_LEVEL) Integer difficultyEnd,
@NotNull @Range(min = 1, max = MAX_PROBLEM_COUNT) Integer problemCount
) {

public RegisterAlgorithmStudyCommand toServiceDto() {
Expand All @@ -40,8 +51,20 @@ public RegisterAlgorithmStudyCommand toServiceDto() {
.difficultyBegin(difficultyBegin)
.difficultyEnd(difficultyEnd)
.problemCount(problemCount)
.state(StudyStatus.READY)
.headCount(0)
.build();

}

@AssertTrue
private boolean isDifficultyBeginLteDifficultyEnd() {
return difficultyBegin <= difficultyEnd;
}

@AssertTrue
private boolean isStartDateAfterOrEqualToday() {
LocalDate now = LocalDate.now();
return startDate.isAfter(now) || startDate.isEqual(now);
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
package com.bombombom.devs.study.controller.dto.request;

import static com.bombombom.devs.study.Constants.MAX_CAPACITY;
import static com.bombombom.devs.study.Constants.MAX_PENALTY;
import static com.bombombom.devs.study.Constants.MAX_RELIABLITY_LIMIT;
import static com.bombombom.devs.study.Constants.MAX_WEEKS;

import com.bombombom.devs.study.models.StudyStatus;
import com.bombombom.devs.study.service.dto.command.RegisterBookStudyCommand;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.time.LocalDate;
import lombok.Builder;
import org.hibernate.validator.constraints.Range;

@Builder
public record RegisterBookStudyRequest(
@NotNull String name,
@NotNull String introduce,
Integer capacity,
Integer weeks,
@NotBlank @Size(max = 255, message = "μŠ€ν„°λ””λͺ…은 255자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€.") String name,
@NotBlank @Size(max = 500, message = "μŠ€ν„°λ””μ†Œκ°œλŠ” 500자λ₯Ό λ„˜μ„ 수 μ—†μŠ΅λ‹ˆλ‹€.") String introduce,
@NotNull @Range(min = 1, max = MAX_CAPACITY) Integer capacity,
@NotNull @Range(min = 1, max = MAX_WEEKS) Integer weeks,
@NotNull LocalDate startDate,
Integer reliabilityLimit,
Integer penalty,
Long bookId
@NotNull @Range(max = MAX_RELIABLITY_LIMIT) Integer reliabilityLimit,
@NotNull @Range(max = MAX_PENALTY) Integer penalty,
@NotNull Long bookId
) {

public RegisterBookStudyCommand toServiceDto() {
Expand All @@ -28,9 +38,16 @@ public RegisterBookStudyCommand toServiceDto() {
.reliabilityLimit(reliabilityLimit)
.penalty(penalty)
.bookId(bookId)
.state(StudyStatus.READY)
.headCount(0)
.build();

}

@AssertTrue
private boolean isStartDateAfterOrEqualToday() {
LocalDate now = LocalDate.now();
return startDate.isAfter(now) || startDate.isEqual(now);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public record AlgorithmStudyResponse(
Integer problemCount)
implements StudyResponse {

public static AlgorithmStudyResponse of(AlgorithmStudyResult res) {
public static AlgorithmStudyResponse fromResult(AlgorithmStudyResult res) {

return builder()
.id(res.id())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public record BookStudyResponse(
Long bookId)
implements StudyResponse {

public static BookStudyResponse of(BookStudyResult res) {
public static BookStudyResponse fromResult(BookStudyResult res) {

return builder()
.id(res.id())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ public interface StudyResponse {
StudyStatus state();

StudyType studyType();

static StudyResponse of(StudyResult study) {
static StudyResponse fromResult(StudyResult study) {

if (study instanceof AlgorithmStudyResult algorithmStudyResult) {
return AlgorithmStudyResponse.of(algorithmStudyResult);
return AlgorithmStudyResponse.fromResult(algorithmStudyResult);
} else if (study instanceof BookStudyResult bookStudyResult) {
return BookStudyResponse.of(bookStudyResult);
return BookStudyResponse.fromResult(bookStudyResult);
} else {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
@Table(name = "algorithm_study")
@DiscriminatorValue(StudyType.Values.ALGORITHM)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicInsert
public class AlgorithmStudy extends Study {

@Column(name = "difficulty_math")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
@Table(name = "book_study")
@DiscriminatorValue(StudyType.Values.BOOK)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicInsert
public class BookStudy extends Study {

@NotNull
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/com/bombombom/devs/study/models/Study.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Inheritance;
import jakarta.persistence.InheritanceType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
Expand All @@ -30,7 +33,6 @@
@DiscriminatorColumn(name = "STUDY_TYPE")
@Inheritance(strategy = InheritanceType.JOINED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicInsert
public abstract class Study extends BaseEntity {

@Id
Expand All @@ -55,6 +57,10 @@ public abstract class Study extends BaseEntity {
@Column
protected Integer weeks;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "leader")
private User user;

@Column(name = "start_date")
protected LocalDate startDate;

Expand All @@ -70,12 +76,6 @@ public abstract class Study extends BaseEntity {

abstract public StudyType getStudyType();

@PrePersist
private void onCreate() {
state = StudyStatus.READY;
headCount = 1;
}

public UserStudy join(User user) {
if (state.equals(StudyStatus.END)) {
throw new IllegalStateException("The Study is End");
Expand Down
Loading

0 comments on commit dedd74b

Please sign in to comment.