-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 스토리 등록 구현 #80
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
[Feat] 스토리 등록 구현 #80
Changes from all commits
6658782
2b3d6ce
529b7e8
8070da7
4ab8d8c
8185eae
90a90e6
111037a
b25be03
d17098f
919a41a
4572bec
089df85
5103492
bf0f4e3
0ac5a65
00483e4
36a230a
47a38bf
10f95c8
d819c60
510546d
5bc6fe2
d9d62ed
d3b7bb9
70beb7d
f716899
4ae9785
e527cf5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package eatda.controller.story; | ||
|
|
||
| public record FilteredSearchResult( | ||
| String kakaoId, | ||
| String name, | ||
| String address, | ||
| String category | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package eatda.controller.story; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record StoriesResponse( | ||
| List<StoryPreview> stories | ||
| ) { | ||
| public record StoryPreview( | ||
| Long storyId, | ||
| String imageUrl | ||
| ) { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package eatda.controller.story; | ||
|
|
||
| import eatda.controller.web.auth.LoginMember; | ||
| import eatda.service.story.StoryService; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestPart; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| public class StoryController { | ||
|
|
||
| private final StoryService storyService; | ||
|
|
||
| @PostMapping("/api/stories") | ||
| public ResponseEntity<Void> registerStory( | ||
| @RequestPart("request") StoryRegisterRequest request, | ||
| @RequestPart("image") MultipartFile image, | ||
| LoginMember member | ||
| ) { | ||
| storyService.registerStory(request, image, member.id()); | ||
| return ResponseEntity.status(HttpStatus.CREATED).build(); | ||
| } | ||
|
Comment on lines
+19
to
+27
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [제안] POST 요청에서
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 생각해보니 스토리 등록후에 어디로 리다이렉트 될지 명확하지 않은 상황이네요. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| package eatda.controller.story; | ||
|
|
||
| public record StoryRegisterRequest( | ||
| String query, | ||
| String storeKakaoId, | ||
| String description | ||
| ) { | ||
| } | ||
lvalentine6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package eatda.controller.story; | ||
|
|
||
| public record StoryResponse( | ||
| String storeKakaoId, | ||
| String category, | ||
| String storeName, | ||
| String storeAddress, | ||
| String description, | ||
| String imageUrl | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| package eatda.domain.story; | ||
|
|
||
| import eatda.domain.AuditingEntity; | ||
| import eatda.domain.member.Member; | ||
| import eatda.exception.BusinessErrorCode; | ||
| import eatda.exception.BusinessException; | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.FetchType; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.JoinColumn; | ||
| import jakarta.persistence.ManyToOne; | ||
| import jakarta.persistence.Table; | ||
| import lombok.AccessLevel; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Table(name = "story") | ||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| public class Story extends AuditingEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "member_id", nullable = false) | ||
| private Member member; | ||
|
|
||
| @Column(name = "store_kakao_id", nullable = false) | ||
| private String storeKakaoId; | ||
|
|
||
| @Column(name = "store_name", nullable = false) | ||
| private String storeName; | ||
|
|
||
| @Column(name = "store_address", nullable = false) | ||
| private String storeAddress; | ||
|
|
||
| @Column(name = "store_category", nullable = false) | ||
| private String storeCategory; | ||
|
Comment on lines
+44
to
+45
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. category 는
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아! 이전에 만들어둔 StoreCategory enum이 있었네요 |
||
|
|
||
| @Column(name = "description", nullable = false) | ||
| private String description; | ||
|
|
||
| @Column(name = "image_key", nullable = false) | ||
| private String imageKey; | ||
lvalentine6 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Builder | ||
| private Story( | ||
| Member member, | ||
| String storeKakaoId, | ||
| String storeName, | ||
| String storeAddress, | ||
| String storeCategory, | ||
| String description, | ||
| String imageKey | ||
| ) { | ||
| validateMember(member); | ||
| validateStore(storeKakaoId, storeName, storeAddress, storeCategory); | ||
| validateStory(description, imageKey); | ||
|
|
||
| this.member = member; | ||
| this.storeKakaoId = storeKakaoId; | ||
| this.storeName = storeName; | ||
| this.storeAddress = storeAddress; | ||
| this.storeCategory = storeCategory; | ||
| this.description = description; | ||
| this.imageKey = imageKey; | ||
| } | ||
|
|
||
| private void validateMember(Member member) { | ||
| if (member == null) { | ||
| throw new BusinessException(BusinessErrorCode.STORY_MEMBER_REQUIRED); | ||
| } | ||
| } | ||
|
|
||
| private void validateStore(String storeKakaoId, String storeName, String storeAddress, String storeCategory) { | ||
| validateStoreKakaoId(storeKakaoId); | ||
| validateStoreName(storeName); | ||
| validateStoreAddress(storeAddress); | ||
| validateStoreCategory(storeCategory); | ||
| } | ||
|
Comment on lines
+82
to
+87
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [검토] 외부 API를 통해 받는 값들은 검증을 하면 안된다고 생각하는데, Kakao API 문서를 참고하셨다면 크게 문제는 없다고 생각합니다. 혹시 모르니 한 번 더 확인 부탁드릴께요!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오.. Kakao API 문서를 참고해서 작성하고 연동 테스트를 거쳤습니다! 외부 API에서 온 값들은 검증을 하지 않아야 한다는 이유가 무엇일까요? 만약 검증을 하지 않는다면
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
저도 외부에서 들어온 값을 검증해야 한다는 것에는 동의합니다. 그런데 "Kakao API 에서 들어온 값이 우리가 정한 도메인 규칙과 일치하지 않아서 가게가 등록되지 않는 상황"에 대한 염려가 큰 것 같아요. 외부 API에서 들어온 모든 정보가 일정한 형식이라는 보장이 되기는 힘드니까요;; 적어도 "특정 형식이 아닐 경우 설정할 기본값"이 있어야 할 것 같아요.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오... 그런 부분도 있겠네요 |
||
|
|
||
| private void validateStory(String description, String imageKey) { | ||
| validateDescription(description); | ||
| validateImage(imageKey); | ||
| } | ||
|
|
||
| private void validateStoreKakaoId(String storeKakaoId) { | ||
| if (storeKakaoId == null || storeKakaoId.isBlank()) { | ||
| throw new BusinessException(BusinessErrorCode.INVALID_STORE_KAKAO_ID); | ||
| } | ||
| } | ||
|
|
||
| private void validateStoreName(String storeName) { | ||
| if (storeName == null || storeName.isBlank()) { | ||
| throw new BusinessException(BusinessErrorCode.INVALID_STORE_NAME); | ||
| } | ||
| } | ||
|
|
||
| private void validateStoreAddress(String storeAddress) { | ||
| if (storeAddress == null || storeAddress.isBlank()) { | ||
| throw new BusinessException(BusinessErrorCode.INVALID_STORE_ADDRESS); | ||
| } | ||
| } | ||
|
|
||
| private void validateStoreCategory(String storeCategory) { | ||
| if (storeCategory == null || storeCategory.isBlank()) { | ||
| throw new BusinessException(BusinessErrorCode.INVALID_STORE_CATEGORY); | ||
| } | ||
| } | ||
|
|
||
| private void validateDescription(String description) { | ||
| if (description == null || description.isBlank()) { | ||
| throw new BusinessException(BusinessErrorCode.INVALID_STORY_DESCRIPTION); | ||
| } | ||
| } | ||
|
|
||
| private void validateImage(String imageKey) { | ||
| if (imageKey == null || imageKey.isBlank()) { | ||
| throw new BusinessException(BusinessErrorCode.INVALID_STORY_IMAGE_KEY); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package eatda.repository.story; | ||
|
|
||
| import eatda.domain.story.Story; | ||
| import eatda.exception.BusinessErrorCode; | ||
| import eatda.exception.BusinessException; | ||
| import java.util.Optional; | ||
| import org.springframework.data.repository.Repository; | ||
|
|
||
| public interface StoryRepository extends Repository<Story, Long> { | ||
|
|
||
| Story save(Story story); | ||
|
|
||
| Optional<Story> findById(Long id); | ||
|
|
||
| default Story getById(Long id) { | ||
| return findById(id) | ||
| .orElseThrow(() -> new BusinessException(BusinessErrorCode.STORY_NOT_FOUND)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package eatda.service.common; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum ImageDomain { | ||
| ARTICLE("article"), | ||
| STORE("store"), | ||
| MEMBER("member"), | ||
| STORY("story"); | ||
|
|
||
| private final String name; | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.