diff --git a/src/main/java/dgu/choco_express/controller/ChocoController.java b/src/main/java/dgu/choco_express/controller/ChocoController.java new file mode 100644 index 0000000..fe534ee --- /dev/null +++ b/src/main/java/dgu/choco_express/controller/ChocoController.java @@ -0,0 +1,59 @@ +package dgu.choco_express.controller; + +import dgu.choco_express.annotation.UserId; +import dgu.choco_express.dto.choco.request.ChocoCreateRequestDto; +import dgu.choco_express.service.choco.ChocoService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class ChocoController { + private final ChocoService chocoService; + + @PostMapping("/box/{boxId}/choco") + public ResponseEntity createChoco( + @UserId Long userId, + @PathVariable Long boxId, + @RequestBody ChocoCreateRequestDto chocoCreateRequestDto + ) { + return ResponseEntity.created( + chocoService.createChoco(userId, boxId, chocoCreateRequestDto) + ).build(); + } + + @GetMapping("/choco") + public ResponseEntity getChocoList( + @UserId Long userId, + @RequestParam(defaultValue = "1") int page + ) { + return ResponseEntity.ok(chocoService.getChocoList(userId, page)); + } + + @GetMapping("/choco/{chocoId}") + public ResponseEntity getChocoDetails( + @UserId Long userId, + @PathVariable Long chocoId + ) { + return ResponseEntity.ok( + chocoService.getChocoDetails(userId, chocoId) + ); + } + + @GetMapping("/choco/count") + public ResponseEntity getChocoPeek( + @UserId Long userId + ) { + return ResponseEntity.ok(chocoService.getChocoPeek(userId)); + } + + @DeleteMapping("/choco/{chocoId}") + public ResponseEntity deleteChoco( + @UserId Long userId, + @PathVariable Long chocoId + ) { + return ResponseEntity.ok(chocoService.deleteChoco(userId, chocoId)); + } +} diff --git a/src/main/java/dgu/choco_express/domain/Box/Box.java b/src/main/java/dgu/choco_express/domain/box/Box.java similarity index 97% rename from src/main/java/dgu/choco_express/domain/Box/Box.java rename to src/main/java/dgu/choco_express/domain/box/Box.java index a34407c..3c3febf 100644 --- a/src/main/java/dgu/choco_express/domain/Box/Box.java +++ b/src/main/java/dgu/choco_express/domain/box/Box.java @@ -1,4 +1,4 @@ -package dgu.choco_express.domain.Box; +package dgu.choco_express.domain.box; import dgu.choco_express.domain.global.BaseTimeEntity; import dgu.choco_express.domain.user.User; diff --git a/src/main/java/dgu/choco_express/domain/choco/Choco.java b/src/main/java/dgu/choco_express/domain/choco/Choco.java new file mode 100644 index 0000000..b360293 --- /dev/null +++ b/src/main/java/dgu/choco_express/domain/choco/Choco.java @@ -0,0 +1,62 @@ +package dgu.choco_express.domain.choco; + +import dgu.choco_express.domain.box.Box; +import dgu.choco_express.domain.global.BaseTimeEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@Table(name = "choco") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Choco extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "type") + private Short type; + + @Column(name = "nickname") + private String nickname; + + @Column(name = "contents") + private String contents; + + @ManyToOne + @JoinColumn(name = "box_id") + private Box box; + + @Builder + public Choco( + final Short type, + final String nickname, + final String contents, + final Box box + ) { + this.type = type; + this.nickname = nickname; + this.contents = contents; + this.box = box; + } + + public static Choco from( + final Short type, + final String nickname, + final String contents, + final Box box + ) { + return Choco.builder() + .type(type) + .nickname(nickname) + .contents(contents) + .box(box) + .build(); + } +} diff --git a/src/main/java/dgu/choco_express/dto/box/response/BoxCurrentUserRetrieverResponseDto.java b/src/main/java/dgu/choco_express/dto/box/response/BoxCurrentUserRetrieverResponseDto.java index 8311e7d..6915358 100644 --- a/src/main/java/dgu/choco_express/dto/box/response/BoxCurrentUserRetrieverResponseDto.java +++ b/src/main/java/dgu/choco_express/dto/box/response/BoxCurrentUserRetrieverResponseDto.java @@ -1,7 +1,7 @@ package dgu.choco_express.dto.box.response; import com.fasterxml.jackson.annotation.JsonProperty; -import dgu.choco_express.domain.Box.Box; +import dgu.choco_express.domain.box.Box; import lombok.Builder; @Builder diff --git a/src/main/java/dgu/choco_express/dto/box/response/BoxOtherUserRetrieverResponseDto.java b/src/main/java/dgu/choco_express/dto/box/response/BoxOtherUserRetrieverResponseDto.java index 8488173..5450777 100644 --- a/src/main/java/dgu/choco_express/dto/box/response/BoxOtherUserRetrieverResponseDto.java +++ b/src/main/java/dgu/choco_express/dto/box/response/BoxOtherUserRetrieverResponseDto.java @@ -1,7 +1,7 @@ package dgu.choco_express.dto.box.response; import com.fasterxml.jackson.annotation.JsonProperty; -import dgu.choco_express.domain.Box.Box; +import dgu.choco_express.domain.box.Box; import lombok.Builder; @Builder diff --git a/src/main/java/dgu/choco_express/dto/choco/request/ChocoCreateRequestDto.java b/src/main/java/dgu/choco_express/dto/choco/request/ChocoCreateRequestDto.java new file mode 100644 index 0000000..591a9d6 --- /dev/null +++ b/src/main/java/dgu/choco_express/dto/choco/request/ChocoCreateRequestDto.java @@ -0,0 +1,15 @@ +package dgu.choco_express.dto.choco.request; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ChocoCreateRequestDto( + @JsonProperty("nickname") + String nickname, + + @JsonProperty("contents") + String contents, + + @JsonProperty("chocoType") + Short chocoType +) { +} diff --git a/src/main/java/dgu/choco_express/dto/choco/response/ChocoDetailsResponseDto.java b/src/main/java/dgu/choco_express/dto/choco/response/ChocoDetailsResponseDto.java new file mode 100644 index 0000000..9e84028 --- /dev/null +++ b/src/main/java/dgu/choco_express/dto/choco/response/ChocoDetailsResponseDto.java @@ -0,0 +1,25 @@ +package dgu.choco_express.dto.choco.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import dgu.choco_express.domain.choco.Choco; +import lombok.Builder; + +@Builder +public record ChocoDetailsResponseDto( + @JsonProperty("id") + Long id, + + @JsonProperty("nickname") + String nickname, + + @JsonProperty("contents") + String contents +) { + public static ChocoDetailsResponseDto of(Choco choco) { + return ChocoDetailsResponseDto.builder() + .id(choco.getId()) + .nickname(choco.getNickname()) + .contents(choco.getContents()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/dgu/choco_express/dto/choco/response/ChocoListResponseDto.java b/src/main/java/dgu/choco_express/dto/choco/response/ChocoListResponseDto.java new file mode 100644 index 0000000..b4fc933 --- /dev/null +++ b/src/main/java/dgu/choco_express/dto/choco/response/ChocoListResponseDto.java @@ -0,0 +1,25 @@ +package dgu.choco_express.dto.choco.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; + +import java.util.List; + +@Builder +public record ChocoListResponseDto( + @JsonProperty("chocoList") + List chocoObjectResponseDtoList, + + @JsonProperty("totalPage") + Integer totalPage +) { + public static ChocoListResponseDto of( + final List chocoObjectResponseDtoList, + final Integer totalPage + ) { + return ChocoListResponseDto.builder() + .chocoObjectResponseDtoList(chocoObjectResponseDtoList) + .totalPage(totalPage) + .build(); + } +} diff --git a/src/main/java/dgu/choco_express/dto/choco/response/ChocoObjectResponseDto.java b/src/main/java/dgu/choco_express/dto/choco/response/ChocoObjectResponseDto.java new file mode 100644 index 0000000..8f1bc93 --- /dev/null +++ b/src/main/java/dgu/choco_express/dto/choco/response/ChocoObjectResponseDto.java @@ -0,0 +1,21 @@ +package dgu.choco_express.dto.choco.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import dgu.choco_express.domain.choco.Choco; +import lombok.Builder; + +@Builder +public record ChocoObjectResponseDto( + @JsonProperty("id") + Long id, + + @JsonProperty("chocoType") + Short chocoType +) { + public static ChocoObjectResponseDto of(final Choco choco) { + return ChocoObjectResponseDto.builder() + .id(choco.getId()) + .chocoType(choco.getType()) + .build(); + } +} diff --git a/src/main/java/dgu/choco_express/dto/choco/response/ChocoPeekResponseDto.java b/src/main/java/dgu/choco_express/dto/choco/response/ChocoPeekResponseDto.java new file mode 100644 index 0000000..555a2dc --- /dev/null +++ b/src/main/java/dgu/choco_express/dto/choco/response/ChocoPeekResponseDto.java @@ -0,0 +1,13 @@ +package dgu.choco_express.dto.choco.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import dgu.choco_express.domain.box.Box; +import lombok.Builder; + +@Builder +public record ChocoPeekResponseDto( + @JsonProperty("count") + Integer count +) { +} + diff --git a/src/main/java/dgu/choco_express/exception/ChocoErrorCode.java b/src/main/java/dgu/choco_express/exception/ChocoErrorCode.java new file mode 100644 index 0000000..3f1326b --- /dev/null +++ b/src/main/java/dgu/choco_express/exception/ChocoErrorCode.java @@ -0,0 +1,20 @@ +package dgu.choco_express.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ChocoErrorCode implements ErrorCode { + INVALID_CHOCO_TYPE(HttpStatus.BAD_REQUEST, "CHOCO_001", "초코 타입이 유효하지 않습니다."), + NOT_FOUND_CHOCO(HttpStatus.NOT_FOUND, "CHOCO_002", "해당 초코가 존재하지 않습니다."), + INVALID_CHOCO_NAME(HttpStatus.BAD_REQUEST, "CHOCO_003", "초코 작성자 이름이 비어있습니다."), + CANT_CHOCO_RECURSIVE(HttpStatus.BAD_REQUEST, "CHOCO_004", "자기 자신에게 초코를 보낼 수 없습니다."), + INVALID_PAGE_CHOCO(HttpStatus.BAD_REQUEST, "CHOCO_005", "유효하지 않은 페이지 넘버입니다."), + ; + + private final HttpStatus status; + private final String errorCode; + private final String message; +} diff --git a/src/main/java/dgu/choco_express/repository/BoxRepository.java b/src/main/java/dgu/choco_express/repository/BoxRepository.java index 4620b64..2fc884b 100644 --- a/src/main/java/dgu/choco_express/repository/BoxRepository.java +++ b/src/main/java/dgu/choco_express/repository/BoxRepository.java @@ -1,6 +1,6 @@ package dgu.choco_express.repository; -import dgu.choco_express.domain.Box.Box; +import dgu.choco_express.domain.box.Box; import dgu.choco_express.domain.user.User; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/dgu/choco_express/repository/ChocoRepository.java b/src/main/java/dgu/choco_express/repository/ChocoRepository.java new file mode 100644 index 0000000..7f9ca8a --- /dev/null +++ b/src/main/java/dgu/choco_express/repository/ChocoRepository.java @@ -0,0 +1,25 @@ +package dgu.choco_express.repository; + +import dgu.choco_express.domain.box.Box; +import dgu.choco_express.domain.choco.Choco; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ChocoRepository extends JpaRepository { + @Query("SELECT count(*) FROM Choco WHERE box = :boxId") + Integer findChocoCountByBox(@Param("boxId") Box box); + + @Query( + value = "SELECT * " + + "FROM choco c " + + "WHERE c.box_id = :boxId", + countQuery = "SELECT COUNT(*) " + + "FROM choco c " + + "WHERE c.box_id = :boxId", + nativeQuery = true + ) + Page findAllByBoxId(Long boxId, Pageable pageable); +} diff --git a/src/main/java/dgu/choco_express/service/box/BoxRetriever.java b/src/main/java/dgu/choco_express/service/box/BoxRetriever.java index 49cc984..e690e23 100644 --- a/src/main/java/dgu/choco_express/service/box/BoxRetriever.java +++ b/src/main/java/dgu/choco_express/service/box/BoxRetriever.java @@ -1,6 +1,6 @@ package dgu.choco_express.service.box; -import dgu.choco_express.domain.Box.Box; +import dgu.choco_express.domain.box.Box; import dgu.choco_express.domain.user.User; import dgu.choco_express.exception.BoxErrorCode; import dgu.choco_express.exception.CommonException; diff --git a/src/main/java/dgu/choco_express/service/box/BoxSaver.java b/src/main/java/dgu/choco_express/service/box/BoxSaver.java index 979f0f9..f8e400c 100644 --- a/src/main/java/dgu/choco_express/service/box/BoxSaver.java +++ b/src/main/java/dgu/choco_express/service/box/BoxSaver.java @@ -1,6 +1,6 @@ package dgu.choco_express.service.box; -import dgu.choco_express.domain.Box.Box; +import dgu.choco_express.domain.box.Box; import dgu.choco_express.repository.BoxRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/dgu/choco_express/service/box/BoxService.java b/src/main/java/dgu/choco_express/service/box/BoxService.java index 7fae52f..3f6e6e8 100644 --- a/src/main/java/dgu/choco_express/service/box/BoxService.java +++ b/src/main/java/dgu/choco_express/service/box/BoxService.java @@ -1,6 +1,6 @@ package dgu.choco_express.service.box; -import dgu.choco_express.domain.Box.Box; +import dgu.choco_express.domain.box.Box; import dgu.choco_express.domain.user.User; import dgu.choco_express.dto.box.request.BoxCreateRequestDto; import dgu.choco_express.dto.box.request.BoxPatchRequestDto; diff --git a/src/main/java/dgu/choco_express/service/box/BoxUpdater.java b/src/main/java/dgu/choco_express/service/box/BoxUpdater.java index c04bd94..61d2fcb 100644 --- a/src/main/java/dgu/choco_express/service/box/BoxUpdater.java +++ b/src/main/java/dgu/choco_express/service/box/BoxUpdater.java @@ -1,6 +1,6 @@ package dgu.choco_express.service.box; -import dgu.choco_express.domain.Box.Box; +import dgu.choco_express.domain.box.Box; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/dgu/choco_express/service/choco/ChocoRemover.java b/src/main/java/dgu/choco_express/service/choco/ChocoRemover.java new file mode 100644 index 0000000..0285ae3 --- /dev/null +++ b/src/main/java/dgu/choco_express/service/choco/ChocoRemover.java @@ -0,0 +1,20 @@ +package dgu.choco_express.service.choco; + +import dgu.choco_express.domain.choco.Choco; +import dgu.choco_express.repository.ChocoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +public class ChocoRemover { + private final ChocoRepository chocoRepository; + + @Transactional + public void deleteChoco( + Choco choco + ) { + chocoRepository.delete(choco); + } +} diff --git a/src/main/java/dgu/choco_express/service/choco/ChocoRetriever.java b/src/main/java/dgu/choco_express/service/choco/ChocoRetriever.java new file mode 100644 index 0000000..c479874 --- /dev/null +++ b/src/main/java/dgu/choco_express/service/choco/ChocoRetriever.java @@ -0,0 +1,30 @@ +package dgu.choco_express.service.choco; + +import dgu.choco_express.domain.box.Box; +import dgu.choco_express.domain.choco.Choco; +import dgu.choco_express.exception.ChocoErrorCode; +import dgu.choco_express.exception.CommonException; +import dgu.choco_express.repository.ChocoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ChocoRetriever { + private final ChocoRepository chocoRepository; + + public Choco findById(Long id) { + return chocoRepository.findById(id) + .orElseThrow(() -> CommonException.type(ChocoErrorCode.NOT_FOUND_CHOCO)); + } + + public Page findAllByBoxId(Long boxId, Pageable pageable) { + return chocoRepository.findAllByBoxId(boxId, pageable); + } + + public Integer findChocoCountByBox(Box box) { + return chocoRepository.findChocoCountByBox(box); + } +} diff --git a/src/main/java/dgu/choco_express/service/choco/ChocoSaver.java b/src/main/java/dgu/choco_express/service/choco/ChocoSaver.java new file mode 100644 index 0000000..06b8172 --- /dev/null +++ b/src/main/java/dgu/choco_express/service/choco/ChocoSaver.java @@ -0,0 +1,18 @@ +package dgu.choco_express.service.choco; + +import dgu.choco_express.domain.choco.Choco; +import dgu.choco_express.repository.ChocoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +@RequiredArgsConstructor +public class ChocoSaver { + private final ChocoRepository chocoRepository; + + @Transactional + public Choco saveChoco(Choco choco) { + return chocoRepository.save(choco); + } +} diff --git a/src/main/java/dgu/choco_express/service/choco/ChocoService.java b/src/main/java/dgu/choco_express/service/choco/ChocoService.java new file mode 100644 index 0000000..e4b8442 --- /dev/null +++ b/src/main/java/dgu/choco_express/service/choco/ChocoService.java @@ -0,0 +1,119 @@ +package dgu.choco_express.service.choco; + +import dgu.choco_express.domain.box.Box; +import dgu.choco_express.domain.choco.Choco; +import dgu.choco_express.domain.user.User; +import dgu.choco_express.dto.choco.request.ChocoCreateRequestDto; +import dgu.choco_express.dto.choco.response.ChocoDetailsResponseDto; +import dgu.choco_express.dto.choco.response.ChocoListResponseDto; +import dgu.choco_express.dto.choco.response.ChocoObjectResponseDto; +import dgu.choco_express.dto.choco.response.ChocoPeekResponseDto; +import dgu.choco_express.exception.ChocoErrorCode; +import dgu.choco_express.exception.CommonException; +import dgu.choco_express.service.box.BoxRetriever; +import dgu.choco_express.service.user.UserRetriever; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ChocoService { + private final UserRetriever userRetriever; + private final ChocoSaver chocoSaver; + private final BoxRetriever boxRetriever; + private final ChocoRetriever chocoRetriever; + private final ChocoRemover chocoRemover; + + public URI createChoco( + Long userId, + Long boxId, + ChocoCreateRequestDto chocoCreateRequestDto + ) { + User currentUser = userRetriever.findById(userId); + + Short chocoType = chocoCreateRequestDto.chocoType(); + String chocoNickname = chocoCreateRequestDto.nickname(); + String chocoContents = chocoCreateRequestDto.contents(); + Box currentBox = boxRetriever.findById(boxId); + + + if (chocoType < 1 || chocoType > 6) + throw CommonException.type(ChocoErrorCode.INVALID_CHOCO_TYPE); + if (chocoNickname.isEmpty()) + throw CommonException.type(ChocoErrorCode.INVALID_CHOCO_NAME); + + Choco createdChoco = chocoSaver.saveChoco( + Choco.from(chocoType, chocoNickname, chocoContents, currentBox) + ); + + return URI.create("/api/choco/" + createdChoco.getId().toString()); + } + + public ChocoListResponseDto getChocoList( + Long userId, + int page + ) { + User currentUser = userRetriever.findById(userId); + Box box = boxRetriever.findByUser(currentUser); + + if (page < 1) + throw CommonException.type(ChocoErrorCode.INVALID_PAGE_CHOCO); + + int size = (box.getType() == 4) ? 6 : 9; + Pageable pageable = PageRequest.of(page - 1, size); + Page chocoPage + = chocoRetriever.findAllByBoxId(box.getId(), pageable); + + if (page != 1 && page > chocoPage.getTotalPages()) + throw CommonException.type(ChocoErrorCode.INVALID_PAGE_CHOCO); + + List chocoObjectResponseDtoList + = chocoPage.getContent().stream() + .map(ChocoObjectResponseDto::of) + .toList(); + + return ChocoListResponseDto.of( + chocoObjectResponseDtoList, + chocoPage.getTotalPages() + ); + } + + public ChocoDetailsResponseDto getChocoDetails( + Long userId, + Long chocoId + ) { + User currentUser = userRetriever.findById(userId); + Choco choco = chocoRetriever.findById(chocoId); + + return ChocoDetailsResponseDto.of(choco); + } + + public ChocoPeekResponseDto getChocoPeek( + Long userId + ) { + User currentUser = userRetriever.findById(userId); + Box currentBox = boxRetriever.findByUser(currentUser); + + return ChocoPeekResponseDto.builder() + .count(chocoRetriever.findChocoCountByBox(currentBox)) + .build(); + } + + public Void deleteChoco( + Long userId, + Long chocoId + ) { + User currentUser = userRetriever.findById(userId); + Choco choco = chocoRetriever.findById(chocoId); + + chocoRemover.deleteChoco(choco); + + return null; + } +}