-
Notifications
You must be signed in to change notification settings - Fork 1
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: 태그 생성, 조회, 업데이트 구현 #17
base: develop
Are you sure you want to change the base?
Changes from all commits
266a271
745da7f
195f49c
7a0df56
e578b49
8c98586
294a878
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,11 @@ | ||
package keepsake.ourmemory.application.repository; | ||
|
||
import keepsake.ourmemory.domain.tag.Tag; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import java.util.List; | ||
|
||
public interface TagRepository extends JpaRepository<Tag, Long> { | ||
|
||
List<Tag> findTagsByMemberIdAndDeletedIsFalse(Long memberId); | ||
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,42 @@ | ||
package keepsake.ourmemory.application.tag; | ||
|
||
import keepsake.ourmemory.application.repository.TagRepository; | ||
import keepsake.ourmemory.application.tag.dto.TagFindResponseDto; | ||
import keepsake.ourmemory.domain.tag.Tag; | ||
import keepsake.ourmemory.domain.tag.TagColor; | ||
import keepsake.ourmemory.domain.tag.TagName; | ||
import keepsake.ourmemory.ui.tag.dto.TagCreateRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.List; | ||
|
||
@Transactional | ||
@RequiredArgsConstructor | ||
@Service | ||
public class TagService { | ||
|
||
private final TagRepository tagRepository; | ||
|
||
public void createTag(TagCreateRequest request) { | ||
TagName tagName = new TagName(request.getTagName()); | ||
TagColor tagColor = new TagColor(request.getTagColor()); | ||
Long memberId = request.getMemberId(); | ||
|
||
Tag tag = new Tag(memberId, tagName, tagColor); | ||
tagRepository.save(tag); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public List<TagFindResponseDto> findTagsByMember(Long memberId) { | ||
List<Tag> tags = tagRepository.findTagsByMemberIdAndDeletedIsFalse(memberId); | ||
return tags.stream() | ||
.map(TagFindResponseDto::from) | ||
.toList(); | ||
} | ||
|
||
public void deleteTagById(Long tagId) { | ||
tagRepository.deleteById(tagId); | ||
} | ||
Comment on lines
+39
to
+41
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. 소프트 딜리트이면 deleteById가 아니라, 해당 태그를 가져와서 isDeleted 부분을 변경 감지를 통해 수정하여, update 쿼리를 날려야할 것 같은데 맞을까요!??! 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. 저는 아마 레오씨의 대답기다리겠습니다. 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. Tag엔티티에 선언한 어노테이션으로 deleted쿼리가 나가면 자동으로 update쿼리가 나갑니다! |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package keepsake.ourmemory.application.tag.dto; | ||
|
||
import keepsake.ourmemory.domain.tag.Tag; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
@AllArgsConstructor | ||
@Getter | ||
public class TagFindResponseDto { | ||
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으로 막아주는 것도 좋을 것 같아요! |
||
|
||
private Long tagId; | ||
private String tagName; | ||
private String tagColor; | ||
|
||
public static TagFindResponseDto from(Tag tag) { | ||
return new TagFindResponseDto( | ||
tag.getId(), | ||
tag.getTagNameValue(), | ||
tag.getTagColorValue() | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,17 @@ | ||
package keepsake.ourmemory.domain.tag; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Embedded; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.*; | ||
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. 인텔리제이 포맷팅 설정이 잘못된 것 같아요!! |
||
import keepsake.ourmemory.domain.BaseEntity; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import org.hibernate.annotations.SQLDelete; | ||
|
||
import java.util.Objects; | ||
|
||
@Entity | ||
@Getter | ||
@SQLDelete(sql = "UPDATE tag SET deleted = true WHERE id = ?") | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class Tag extends BaseEntity { | ||
@Id | ||
|
@@ -30,6 +27,9 @@ public class Tag extends BaseEntity { | |
@Embedded | ||
private TagColor tagColor; | ||
|
||
@Column(nullable = false) | ||
private boolean deleted = false; | ||
|
||
public Tag(Long memberId, TagName tagName, TagColor tagColor) { | ||
this.memberId = memberId; | ||
this.tagName = tagName; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package keepsake.ourmemory.ui.tag; | ||
|
||
import keepsake.ourmemory.application.tag.TagService; | ||
import keepsake.ourmemory.application.tag.dto.TagFindResponseDto; | ||
import keepsake.ourmemory.ui.tag.dto.TagCreateRequest; | ||
import keepsake.ourmemory.ui.tag.dto.TagFindResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.util.List; | ||
|
||
@RequiredArgsConstructor | ||
@RestController | ||
public class TagController { | ||
|
||
private final TagService tagService; | ||
|
||
@PostMapping("/tags") | ||
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.
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. 공감합니다 |
||
public ResponseEntity<Void> createTag(@RequestBody TagCreateRequest request) { | ||
tagService.createTag(request); | ||
return ResponseEntity.status(HttpStatus.CREATED).build(); | ||
} | ||
|
||
@GetMapping("/tags/members/{memberId}") | ||
public ResponseEntity<TagFindResponse> findTagsByMember(@PathVariable Long memberId) { | ||
List<TagFindResponseDto> tags = tagService.findTagsByMember(memberId); | ||
return ResponseEntity.ok(new TagFindResponse(tags)); | ||
} | ||
|
||
@DeleteMapping("tags/{tagId}") | ||
public ResponseEntity<Void> deleteTag(@PathVariable Long tagId) { | ||
tagService.deleteTagById(tagId); | ||
return ResponseEntity.noContent().build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package keepsake.ourmemory.ui.tag.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
@Getter | ||
public class TagCreateRequest { | ||
|
||
private Long memberId; | ||
private String tagName; | ||
private String tagColor; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package keepsake.ourmemory.ui.tag.dto; | ||
|
||
import keepsake.ourmemory.application.tag.dto.TagFindResponseDto; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import java.util.List; | ||
|
||
import static lombok.AccessLevel.PROTECTED; | ||
|
||
@AllArgsConstructor | ||
@NoArgsConstructor(access = PROTECTED) | ||
@Getter | ||
public class TagFindResponse { | ||
|
||
private List<TagFindResponseDto> tags; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package keepsake.ourmemory.application.repository; | ||
|
||
import keepsake.ourmemory.domain.tag.Tag; | ||
import keepsake.ourmemory.domain.tag.TagColor; | ||
import keepsake.ourmemory.domain.tag.TagName; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; | ||
|
||
import java.util.List; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
@DataJpaTest | ||
class TagRepositoryTest { | ||
|
||
@Autowired | ||
private TagRepository tagRepository; | ||
|
||
@Test | ||
void 멤버의_태그를_조회한다() { | ||
// given | ||
Long memberId = 1L; | ||
Tag tag1 = new Tag(memberId, new TagName("tagName1"), new TagColor("tagColor1")); | ||
Tag tag2 = new Tag(memberId, new TagName("tagName2"), new TagColor("tagColor2")); | ||
Tag tag3 = new Tag(2L, new TagName("tagName3"), new TagColor("tagColor3")); | ||
|
||
tagRepository.save(tag1); | ||
tagRepository.save(tag2); | ||
tagRepository.save(tag3); | ||
|
||
// when | ||
List<Tag> tagsByMember = tagRepository.findTagsByMemberIdAndDeletedIsFalse(memberId); | ||
|
||
// then | ||
assertThat(tagsByMember).usingRecursiveFieldByFieldElementComparatorIgnoringFields("id") | ||
.containsExactlyInAnyOrder(tag1, tag2); | ||
} | ||
|
||
@Test | ||
void 멤버의_태그를_삭제한다() { | ||
// given | ||
Long memberId = 1L; | ||
Tag tag1 = new Tag(memberId, new TagName("tagName1"), new TagColor("tagColor1")); | ||
Tag tag2 = new Tag(memberId, new TagName("tagName2"), new TagColor("tagColor2")); | ||
|
||
Tag savedTag1 = tagRepository.save(tag1); | ||
Tag savedTag2 = tagRepository.save(tag2); | ||
|
||
// when | ||
tagRepository.deleteById(savedTag2.getId()); | ||
List<Tag> tagsByMember = tagRepository.findTagsByMemberIdAndDeletedIsFalse(memberId); | ||
// then | ||
assertThat(tagsByMember).usingRecursiveFieldByFieldElementComparatorIgnoringFields("id") | ||
.containsExactlyInAnyOrder(savedTag1); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package keepsake.ourmemory.application.tag; | ||
|
||
import keepsake.ourmemory.application.repository.TagRepository; | ||
import keepsake.ourmemory.domain.tag.Tag; | ||
import keepsake.ourmemory.domain.tag.TagColor; | ||
import keepsake.ourmemory.domain.tag.TagName; | ||
import keepsake.ourmemory.ui.tag.dto.TagCreateRequest; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.InjectMocks; | ||
import org.mockito.Mock; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.BDDMockito.given; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
class TagServiceTest { | ||
|
||
@InjectMocks | ||
private TagService tagService; | ||
|
||
@Mock | ||
private TagRepository tagRepository; | ||
|
||
@Test | ||
void 태그를_생성한다() { | ||
Tag dummyTag = makeDummyTag(); | ||
given(tagRepository.save(any())) | ||
.willReturn(dummyTag); | ||
|
||
TagCreateRequest request = new TagCreateRequest(1L, "tagName", "tagColor"); | ||
assertDoesNotThrow(() -> tagService.createTag(request)); | ||
} | ||
|
||
private Tag makeDummyTag() { | ||
return new Tag(1L, new TagName("tagNAme"), new TagColor("tagColor")); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package keepsake.ourmemory.ui.tag; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import keepsake.ourmemory.application.tag.TagService; | ||
import keepsake.ourmemory.ui.tag.dto.TagCreateRequest; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||
import org.springframework.boot.test.mock.mockito.MockBean; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
|
||
import static org.springframework.http.MediaType.APPLICATION_JSON; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
|
||
@WebMvcTest(TagController.class) | ||
class TagControllerTest { | ||
|
||
@Autowired | ||
private MockMvc mockMvc; | ||
|
||
@MockBean | ||
private TagService tagService; | ||
|
||
@Autowired | ||
private ObjectMapper objectMapper; | ||
|
||
@Test | ||
void 태그_생성_후_201_을_반환한다() throws Exception { | ||
TagCreateRequest request = new TagCreateRequest(1L, "tagName", "tagColor"); | ||
String jsonRequest = objectMapper.writeValueAsString(request); | ||
|
||
mockMvc.perform(post("/tags") | ||
.content(jsonRequest) | ||
.contentType(APPLICATION_JSON)) | ||
.andExpect(status().isCreated()); | ||
} | ||
|
||
@Test | ||
void 멤버의_태그_조회에_성공하면_200을_반환한다() throws Exception { | ||
mockMvc.perform(get("/tags/members/{memberId}", 1L)) | ||
.andExpect(status().isOk()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오.. 쿼리 메서드로 소프트 딜리트를 이렇게 핸들링할 수 있군여! 따봉