diff --git a/backend/src/main/java/com/woowacourse/moamoa/common/config/WebConfig.java b/backend/src/main/java/com/woowacourse/moamoa/common/config/WebConfig.java index dd31a9973..f5aa63e55 100644 --- a/backend/src/main/java/com/woowacourse/moamoa/common/config/WebConfig.java +++ b/backend/src/main/java/com/woowacourse/moamoa/common/config/WebConfig.java @@ -1,7 +1,6 @@ package com.woowacourse.moamoa.common.config; import java.util.List; - import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -21,7 +20,7 @@ public void addArgumentResolvers(final List resol @Override public void addCorsMappings(final CorsRegistry registry) { registry.addMapping("/api/**") - .allowedMethods(ALLOW_METHODS) - .exposedHeaders(HttpHeaders.LOCATION); + .allowedMethods(ALLOW_METHODS) + .exposedHeaders(HttpHeaders.LOCATION); } } diff --git a/backend/src/main/java/com/woowacourse/moamoa/tag/controller/TagController.java b/backend/src/main/java/com/woowacourse/moamoa/tag/controller/TagController.java new file mode 100644 index 000000000..9ad5cb158 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/moamoa/tag/controller/TagController.java @@ -0,0 +1,25 @@ +package com.woowacourse.moamoa.tag.controller; + +import com.woowacourse.moamoa.tag.service.TagService; +import com.woowacourse.moamoa.tag.service.response.TagsResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class TagController { + + private final TagService tagService; + + @GetMapping("/api/tags") + public ResponseEntity getTags( + @RequestParam(value = "tag-name", required = false, defaultValue = "") final String tagName + ) { + final TagsResponse tagsResponse = tagService.getTags(tagName); + + return ResponseEntity.ok().body(tagsResponse); + } +} diff --git a/backend/src/main/java/com/woowacourse/moamoa/tag/domain/Tag.java b/backend/src/main/java/com/woowacourse/moamoa/tag/domain/Tag.java new file mode 100644 index 000000000..14d5b30ed --- /dev/null +++ b/backend/src/main/java/com/woowacourse/moamoa/tag/domain/Tag.java @@ -0,0 +1,26 @@ +package com.woowacourse.moamoa.tag.domain; + +import static javax.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@Getter +@Entity +@NoArgsConstructor(access = PROTECTED) +public class Tag { + + @Id + @GeneratedValue(strategy = IDENTITY) + private Long id; + + @Column(name = "tag_name") + private String name; +} diff --git a/backend/src/main/java/com/woowacourse/moamoa/tag/domain/repository/JpaTagRepository.java b/backend/src/main/java/com/woowacourse/moamoa/tag/domain/repository/JpaTagRepository.java new file mode 100644 index 000000000..6d7f39d09 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/moamoa/tag/domain/repository/JpaTagRepository.java @@ -0,0 +1,8 @@ +package com.woowacourse.moamoa.tag.domain.repository; + +import com.woowacourse.moamoa.tag.domain.Tag; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface JpaTagRepository extends JpaRepository, TagRepository { + +} diff --git a/backend/src/main/java/com/woowacourse/moamoa/tag/domain/repository/TagRepository.java b/backend/src/main/java/com/woowacourse/moamoa/tag/domain/repository/TagRepository.java new file mode 100644 index 000000000..98124abb4 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/moamoa/tag/domain/repository/TagRepository.java @@ -0,0 +1,9 @@ +package com.woowacourse.moamoa.tag.domain.repository; + +import com.woowacourse.moamoa.tag.domain.Tag; +import java.util.List; + +public interface TagRepository { + + List findAllByNameContainingIgnoreCase(String name); +} diff --git a/backend/src/main/java/com/woowacourse/moamoa/tag/service/TagService.java b/backend/src/main/java/com/woowacourse/moamoa/tag/service/TagService.java new file mode 100644 index 000000000..e3e8ec2f8 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/moamoa/tag/service/TagService.java @@ -0,0 +1,27 @@ +package com.woowacourse.moamoa.tag.service; + +import com.woowacourse.moamoa.tag.domain.Tag; +import com.woowacourse.moamoa.tag.domain.repository.TagRepository; +import com.woowacourse.moamoa.tag.service.response.TagResponse; +import com.woowacourse.moamoa.tag.service.response.TagsResponse; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +@Transactional(readOnly = true) +public class TagService { + + private final TagRepository tagRepository; + + public TagsResponse getTags(final String tagName) { + final List tags = tagRepository.findAllByNameContainingIgnoreCase(tagName.trim()); + final List tagsResponse = tags.stream() + .map(TagResponse::new) + .collect(Collectors.toList()); + return new TagsResponse(tagsResponse); + } +} diff --git a/backend/src/main/java/com/woowacourse/moamoa/tag/service/response/TagResponse.java b/backend/src/main/java/com/woowacourse/moamoa/tag/service/response/TagResponse.java new file mode 100644 index 000000000..4dcd3bb6f --- /dev/null +++ b/backend/src/main/java/com/woowacourse/moamoa/tag/service/response/TagResponse.java @@ -0,0 +1,17 @@ +package com.woowacourse.moamoa.tag.service.response; + +import com.woowacourse.moamoa.tag.domain.Tag; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class TagResponse { + + private Long id; + private String tagName; + + public TagResponse(Tag tag) { + this(tag.getId(), tag.getName()); + } +} diff --git a/backend/src/main/java/com/woowacourse/moamoa/tag/service/response/TagsResponse.java b/backend/src/main/java/com/woowacourse/moamoa/tag/service/response/TagsResponse.java new file mode 100644 index 000000000..2e5c0f005 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/moamoa/tag/service/response/TagsResponse.java @@ -0,0 +1,12 @@ +package com.woowacourse.moamoa.tag.service.response; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class TagsResponse { + + private List tags; +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties deleted file mode 100644 index 8b1378917..000000000 --- a/backend/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/backend/src/test/java/com/woowacourse/acceptance/AcceptanceTest.java b/backend/src/test/java/com/woowacourse/acceptance/AcceptanceTest.java index bbe5b72bf..caef0c432 100644 --- a/backend/src/test/java/com/woowacourse/acceptance/AcceptanceTest.java +++ b/backend/src/test/java/com/woowacourse/acceptance/AcceptanceTest.java @@ -26,11 +26,11 @@ void setUp() { @Test void corsTest() { RestAssured.given() - .header("Origin", "https://xxx.com") - .header("Access-Control-Request-Method", "GET") - .when() - .options("/api/studies") - .then() - .statusCode(200); + .header("Origin", "https://xxx.com") + .header("Access-Control-Request-Method", "GET") + .when() + .options("/api/studies") + .then() + .statusCode(200); } } diff --git a/backend/src/test/java/com/woowacourse/acceptance/tag/TagsAcceptanceTest.java b/backend/src/test/java/com/woowacourse/acceptance/tag/TagsAcceptanceTest.java new file mode 100644 index 000000000..2ced9cc56 --- /dev/null +++ b/backend/src/test/java/com/woowacourse/acceptance/tag/TagsAcceptanceTest.java @@ -0,0 +1,56 @@ +package com.woowacourse.acceptance.tag; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.not; + +import com.woowacourse.acceptance.AcceptanceTest; +import io.restassured.RestAssured; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; + +public class TagsAcceptanceTest extends AcceptanceTest { + + @DisplayName("전체 태그 목록을 조회한다.") + @Test + void getAllTags() { + RestAssured.given().log().all() + .when().log().all() + .get("/api/tags") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .body("tags", hasSize(5)) + .body("tags.id", not(empty())) + .body("tags.tagName", contains("Java", "4기", "BE", "FE", "React")); + } + + @DisplayName("공백의 태그 이름일 경우 전체 태그 목록을 조회한다.") + @Test + void getAllTagsByBlankTagName() { + RestAssured.given().log().all() + .queryParam("tag-name", " \t ") + .when().log().all() + .get("/api/tags") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .body("tags", hasSize(5)) + .body("tags.id", not(empty())) + .body("tags.tagName", contains("Java", "4기", "BE", "FE", "React")); + } + + @DisplayName("태그 이름을 포함한 태그 목록을 대소문자 구분없이 조회한다.") + @Test + void getTagsByTagName() { + RestAssured.given().log().all() + .queryParam("tag-name", "ja") + .when().log().all() + .get("/api/tags") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .body("tags", hasSize(1)) + .body("tags.id", not(empty())) + .body("tags.tagName", contains("Java")); + } +} diff --git a/backend/src/test/java/com/woowacourse/moamoa/study/controller/StudyControllerTest.java b/backend/src/test/java/com/woowacourse/moamoa/study/controller/StudyControllerTest.java index fdb04bd04..324426184 100644 --- a/backend/src/test/java/com/woowacourse/moamoa/study/controller/StudyControllerTest.java +++ b/backend/src/test/java/com/woowacourse/moamoa/study/controller/StudyControllerTest.java @@ -117,7 +117,8 @@ void searchByKeyword() { @DisplayName("앞뒤 공백을 제거한 문자열로 스터디 목록 조회") @Test void searchWithTrimKeyword() { - ResponseEntity response = studyController.searchStudies(" Java 스터디 ", PageRequest.of(0, 3)); + ResponseEntity response = studyController + .searchStudies(" Java 스터디 ", PageRequest.of(0, 3)); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isNotNull(); diff --git a/backend/src/test/java/com/woowacourse/moamoa/tag/controller/TagControllerTest.java b/backend/src/test/java/com/woowacourse/moamoa/tag/controller/TagControllerTest.java new file mode 100644 index 000000000..7ab978fa7 --- /dev/null +++ b/backend/src/test/java/com/woowacourse/moamoa/tag/controller/TagControllerTest.java @@ -0,0 +1,73 @@ +package com.woowacourse.moamoa.tag.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.woowacourse.moamoa.tag.domain.Tag; +import com.woowacourse.moamoa.tag.domain.repository.TagRepository; +import com.woowacourse.moamoa.tag.service.TagService; +import com.woowacourse.moamoa.tag.service.response.TagResponse; +import com.woowacourse.moamoa.tag.service.response.TagsResponse; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +class TagControllerTest { + + private TagRepository tagRepository; + private TagController tagController; + + @BeforeEach + void setUp() { + tagRepository = Mockito.mock(TagRepository.class); + when(tagRepository.findAllByNameContainingIgnoreCase("")) + .thenReturn(List.of( + new Tag(1L, "Java"), new Tag(2L, "4기"), new Tag(3L, "BE") + )); + when(tagRepository.findAllByNameContainingIgnoreCase("ja")) + .thenReturn(List.of( + new Tag(1L, "Java") + )); + tagController = new TagController(new TagService(tagRepository)); + } + + @DisplayName("태그 목록 전체를 조회한다.") + @Test + void getTags() { + ResponseEntity response = tagController.getTags(""); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().getTags()) + .extracting(TagResponse::getId, TagResponse::getTagName) + .containsExactly( + tuple(1L, "Java"), + tuple(2L, "4기"), + tuple(3L, "BE") + ); + + verify(tagRepository).findAllByNameContainingIgnoreCase(""); + } + + @DisplayName("태그 이름을 대소문자 구분없이 앞뒤 공백을 제거해 태그 목록을 조회한다.") + @Test + void getTagsByName() { + ResponseEntity response = tagController.getTags(" ja \t "); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().getTags()) + .extracting(TagResponse::getId, TagResponse::getTagName) + .containsExactly( + tuple(1L, "Java") + ); + + verify(tagRepository).findAllByNameContainingIgnoreCase("ja"); + } +} diff --git a/backend/src/test/java/com/woowacourse/moamoa/tag/domain/repository/TagRepositoryTest.java b/backend/src/test/java/com/woowacourse/moamoa/tag/domain/repository/TagRepositoryTest.java new file mode 100644 index 000000000..ac600e754 --- /dev/null +++ b/backend/src/test/java/com/woowacourse/moamoa/tag/domain/repository/TagRepositoryTest.java @@ -0,0 +1,39 @@ +package com.woowacourse.moamoa.tag.domain.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.woowacourse.moamoa.tag.domain.Tag; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +@DataJpaTest +class TagRepositoryTest { + + @Autowired + private TagRepository tagRepository; + + @DisplayName("태그 없이 태그 조회시 태그 목록 전체를 조회한다.") + @Test + void findAllByBlankTagName() { + List tags = tagRepository.findAllByNameContainingIgnoreCase(""); + + assertThat(tags).hasSize(5) + .filteredOn(tag -> tag.getId() != null) + .extracting("name") + .containsExactlyInAnyOrder("Java", "4기", "BE", "FE", "React"); + } + + @DisplayName("대소문자 구분없이 태그 이름으로 조회한다.") + @Test + void findAllByNameContainingIgnoreCase() { + List tags = tagRepository.findAllByNameContainingIgnoreCase("ja"); + + assertThat(tags).hasSize(1) + .filteredOn(tag -> tag.getId() != null) + .extracting("name") + .containsExactlyInAnyOrder("Java"); + } +}