From 72984059babd64a1e4b38f97d56cb82c528489d7 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 16:32:23 +0900 Subject: [PATCH 001/168] =?UTF-8?q?feat(domain):=20Category=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu --- .../codezap/category/domain/Category.java | 20 +++++++++++++++++++ .../codezap/template/domain/Template.java | 5 +++++ 2 files changed, 25 insertions(+) create mode 100644 backend/src/main/java/codezap/category/domain/Category.java diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java new file mode 100644 index 000000000..5654900b6 --- /dev/null +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -0,0 +1,20 @@ +package codezap.category.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import codezap.global.auditing.BaseTimeEntity; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +public class Category extends BaseTimeEntity { + @Id + private Long id; + + @Column(nullable = false) + private String name; +} diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index 1178d79c1..a00b75b63 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -5,7 +5,9 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import codezap.category.domain.Category; import codezap.global.auditing.BaseTimeEntity; import lombok.Getter; import lombok.NoArgsConstructor; @@ -22,6 +24,9 @@ public class Template extends BaseTimeEntity { @Column(nullable = false) private String title; + @ManyToOne(optional = false) + private Category category; + public Template(String title) { this.title = title; } From 865d93dbfbdb30faef9ac1d01b39bf10ea2de0a4 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 16:41:16 +0900 Subject: [PATCH 002/168] =?UTF-8?q?feat(domain):=20Tag=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu --- .../java/codezap/template/domain/Tag.java | 21 +++++++++ .../codezap/template/domain/TemplateTag.java | 43 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 backend/src/main/java/codezap/template/domain/Tag.java create mode 100644 backend/src/main/java/codezap/template/domain/TemplateTag.java diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java new file mode 100644 index 000000000..73106efe6 --- /dev/null +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -0,0 +1,21 @@ +package codezap.template.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import codezap.global.auditing.BaseTimeEntity; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +public class Tag extends BaseTimeEntity { + + @Id + private Long id; + + @Column(nullable = false) + private String value; +} diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java new file mode 100644 index 000000000..7fa96742c --- /dev/null +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -0,0 +1,43 @@ +package codezap.template.domain; + +import java.io.Serializable; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; + +import codezap.global.auditing.BaseTimeEntity; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Entity +@RequiredArgsConstructor +@Getter +public class TemplateTag extends BaseTimeEntity { + + @Embeddable + @RequiredArgsConstructor + @Getter + @EqualsAndHashCode + private static class TemplateTagId implements Serializable { + private Long templateId; + private Long tagId; + } + + @EmbeddedId + private TemplateTagId id; + + @ManyToOne + @MapsId("templateId") + @JoinColumn(name = "template_id") + private Template template; + + @ManyToOne + @MapsId("tagId") + @JoinColumn(name = "tag_id") + private Tag tag; +} \ No newline at end of file From de7d1ae848a084ca79a30e542ef5a72bd63c98a6 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 20:46:04 +0900 Subject: [PATCH 003/168] =?UTF-8?q?refactor(domain):=20Tag=EC=99=80=20Cate?= =?UTF-8?q?gory=EC=97=90=20=EA=B8=B0=EB=B3=B8=20=ED=82=A4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu --- backend/src/main/java/codezap/category/domain/Category.java | 3 +++ backend/src/main/java/codezap/template/domain/Tag.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index 5654900b6..7d96246ed 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -2,6 +2,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import codezap.global.auditing.BaseTimeEntity; @@ -13,6 +15,7 @@ @NoArgsConstructor public class Category extends BaseTimeEntity { @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java index 73106efe6..7859a3f61 100644 --- a/backend/src/main/java/codezap/template/domain/Tag.java +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -2,6 +2,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import codezap.global.auditing.BaseTimeEntity; @@ -14,6 +16,7 @@ public class Tag extends BaseTimeEntity { @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) From b73064b6dc88ca3c32c084cff2f6cd1f0391256c Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 20:50:51 +0900 Subject: [PATCH 004/168] =?UTF-8?q?style(service):=20test=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B0=9C=ED=96=89=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu --- .../java/codezap/template/service/TemplateServiceTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 0bde5fd7a..fea66f4c4 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -34,15 +34,18 @@ @Sql(value = "/clear.sql", executionPhase = ExecutionPhase.AFTER_TEST_CLASS) class TemplateServiceTest { + @LocalServerPort + int port; + @Autowired private TemplateService templateService; - @LocalServerPort - int port; @Autowired private TemplateRepository templateRepository; + @Autowired private SnippetRepository snippetRepository; + @Autowired private ThumbnailSnippetRepository thumbnailSnippetRepository; From 988fff6734e8d2d3bdf0c0fc41e2be2705ecc3f9 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 20:54:28 +0900 Subject: [PATCH 005/168] =?UTF-8?q?feat(category):=20category=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 29 +++++++++++++ .../codezap/category/domain/Category.java | 5 +++ .../dto/request/CreateCategoryRequest.java | 6 +++ .../repository/CategoryRepository.java | 8 ++++ .../category/service/CategoryService.java | 24 +++++++++++ .../controller/CategoryControllerTest.java | 42 ++++++++++++++++++ .../category/service/CategoryServiceTest.java | 43 +++++++++++++++++++ backend/src/test/resources/clear.sql | 34 +++++++++++++++ 8 files changed, 191 insertions(+) create mode 100644 backend/src/main/java/codezap/category/controller/CategoryController.java create mode 100644 backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java create mode 100644 backend/src/main/java/codezap/category/repository/CategoryRepository.java create mode 100644 backend/src/main/java/codezap/category/service/CategoryService.java create mode 100644 backend/src/test/java/codezap/category/controller/CategoryControllerTest.java create mode 100644 backend/src/test/java/codezap/category/service/CategoryServiceTest.java diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java new file mode 100644 index 000000000..982706b6e --- /dev/null +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -0,0 +1,29 @@ +package codezap.category.controller; + +import java.net.URI; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.service.CategoryService; + +@RestController +@RequestMapping("/categories") +public class CategoryController { + + private final CategoryService categoryService; + + public CategoryController(CategoryService categoryService) { + this.categoryService = categoryService; + } + + @PostMapping + public ResponseEntity createCategory(@RequestBody CreateCategoryRequest createCategoryRequest) { + return ResponseEntity.created(URI.create("/categories/" + categoryService.create(createCategoryRequest))) + .build(); + } +} diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index 7d96246ed..0c4aa47da 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -20,4 +20,9 @@ public class Category extends BaseTimeEntity { @Column(nullable = false) private String name; + + public Category(String name) { + this.id = null; + this.name = name; + } } diff --git a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java new file mode 100644 index 000000000..892495166 --- /dev/null +++ b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java @@ -0,0 +1,6 @@ +package codezap.category.dto.request; + +public record CreateCategoryRequest( + String name +) { +} diff --git a/backend/src/main/java/codezap/category/repository/CategoryRepository.java b/backend/src/main/java/codezap/category/repository/CategoryRepository.java new file mode 100644 index 000000000..8b3644a89 --- /dev/null +++ b/backend/src/main/java/codezap/category/repository/CategoryRepository.java @@ -0,0 +1,8 @@ +package codezap.category.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import codezap.category.domain.Category; + +public interface CategoryRepository extends JpaRepository { +} diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java new file mode 100644 index 000000000..7dcb46b59 --- /dev/null +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -0,0 +1,24 @@ +package codezap.category.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import codezap.category.domain.Category; +import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.repository.CategoryRepository; + +@Service +public class CategoryService { + + private final CategoryRepository categoryRepository; + + public CategoryService(CategoryRepository categoryRepository) { + this.categoryRepository = categoryRepository; + } + + @Transactional + public Long create(CreateCategoryRequest createCategoryRequest) { + Category category = new Category(createCategoryRequest.name()); + return categoryRepository.save(category).getId(); + } +} diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java new file mode 100644 index 000000000..00d7cee6f --- /dev/null +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -0,0 +1,42 @@ +package codezap.category.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.Sql.ExecutionPhase; + +import codezap.category.dto.request.CreateCategoryRequest; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@Sql(value = "/clear.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) +@Sql(value = "/clear.sql", executionPhase = ExecutionPhase.AFTER_TEST_CLASS) +class CategoryControllerTest { + + @LocalServerPort + int port; + + @BeforeEach + void setting() { + RestAssured.port = port; + } + + @Test + @DisplayName("카테고리 생성 성공") + void createCategorySuccess() { + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(createCategoryRequest) + .when().post("/categories") + .then().log().all() + .header("Location", "/categories/1") + .statusCode(201); + } +} \ No newline at end of file diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java new file mode 100644 index 000000000..1c98c92b7 --- /dev/null +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -0,0 +1,43 @@ +package codezap.category.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.Sql.ExecutionPhase; + +import codezap.category.dto.request.CreateCategoryRequest; +import io.restassured.RestAssured; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@Sql(value = "/clear.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) +@Sql(value = "/clear.sql", executionPhase = ExecutionPhase.AFTER_TEST_CLASS) +class CategoryServiceTest { + + @LocalServerPort + int port; + + @Autowired + private CategoryService categoryService; + + @BeforeEach + void setting() { + RestAssured.port = port; + } + + @Test + @DisplayName("카테고리 생성 성공") + void createCategorySuccess() { + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category1"); + + Long categoryId = categoryService.create(createCategoryRequest); + + assertThat(categoryId).isEqualTo(1L); + } +} \ No newline at end of file diff --git a/backend/src/test/resources/clear.sql b/backend/src/test/resources/clear.sql index 845a987b2..8cb83de09 100644 --- a/backend/src/test/resources/clear.sql +++ b/backend/src/test/resources/clear.sql @@ -1,16 +1,50 @@ DROP TABLE IF EXISTS thumbnail_snippet; DROP TABLE IF EXISTS snippet; +DROP TABLE IF EXISTS template_tag; +DROP TABLE IF EXISTS tag; DROP TABLE IF EXISTS template; +DROP TABLE IF EXISTS category; + +CREATE TABLE category +( + id BIGINT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + created_at DATETIME(6) NOT NULL, + modified_at DATETIME(6) NOT NULL, + PRIMARY KEY (id) +); CREATE TABLE template ( id BIGINT NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL, + category_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + modified_at DATETIME(6) NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (category_id) REFERENCES category (id) +); + +create table tag +( + id BIGINT NOT NULL auto_increment, + value VARCHAR(255) NOT NULL, created_at DATETIME(6) NOT NULL, modified_at DATETIME(6) NOT NULL, PRIMARY KEY (id) ); +create table template_tag +( + template_id BIGINT NOT NULL, + tag_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + modified_at DATETIME(6) NOT NULL, + PRIMARY KEY (template_id, tag_id), + FOREIGN KEY (template_id) REFERENCES template (id), + FOREIGN KEY (tag_id) REFERENCES tag (id) +); + CREATE TABLE snippet ( id BIGINT NOT NULL AUTO_INCREMENT, From 6b99ee7828411c0533daa2b7f3af9149983ff8ae Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 21:03:23 +0900 Subject: [PATCH 006/168] =?UTF-8?q?feat(category):=20category=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 4 +- .../dto/request/CreateCategoryRequest.java | 5 ++ .../controller/CategoryControllerTest.java | 47 ++++++++++++++----- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 982706b6e..6f546ef1e 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -2,6 +2,8 @@ import java.net.URI; +import jakarta.validation.Valid; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -22,7 +24,7 @@ public CategoryController(CategoryService categoryService) { } @PostMapping - public ResponseEntity createCategory(@RequestBody CreateCategoryRequest createCategoryRequest) { + public ResponseEntity createCategory(@Valid @RequestBody CreateCategoryRequest createCategoryRequest) { return ResponseEntity.created(URI.create("/categories/" + categoryService.create(createCategoryRequest))) .build(); } diff --git a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java index 892495166..7d1276e94 100644 --- a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java +++ b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java @@ -1,6 +1,11 @@ package codezap.category.dto.request; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + public record CreateCategoryRequest( + @NotNull(message = "카테고리 이름이 null 입니다.") + @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.") String name ) { } diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index 00d7cee6f..edfbf41a2 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -1,7 +1,10 @@ package codezap.category.controller; +import static org.hamcrest.Matchers.is; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -18,6 +21,8 @@ @Sql(value = "/clear.sql", executionPhase = ExecutionPhase.AFTER_TEST_CLASS) class CategoryControllerTest { + private static final int MAX_LENGTH = 255; + @LocalServerPort int port; @@ -26,17 +31,35 @@ void setting() { RestAssured.port = port; } - @Test - @DisplayName("카테고리 생성 성공") - void createCategorySuccess() { - CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category"); - - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .body(createCategoryRequest) - .when().post("/categories") - .then().log().all() - .header("Location", "/categories/1") - .statusCode(201); + @Nested + @DisplayName("카테고리 생성 테스트") + class createCategoryTest { + @Test + @DisplayName("카테고리 생성 성공") + void createCategorySuccess() { + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("a".repeat(MAX_LENGTH)); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(createCategoryRequest) + .when().post("/categories") + .then().log().all() + .header("Location", "/categories/1") + .statusCode(201); + } + + @Test + @DisplayName("카테고리 생성 실패:") + void createCategoryFail() { + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("a".repeat(MAX_LENGTH + 1)); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(createCategoryRequest) + .when().post("/categories") + .then().log().all() + .statusCode(400) + .body("detail", is("카테고리 이름은 최대 255자까지 입력 가능합니다.")); + } } } \ No newline at end of file From 292555f0d35c7e5a4bd9356afcf2a17b3f9e15b6 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 21:29:18 +0900 Subject: [PATCH 007/168] =?UTF-8?q?feat(category):=20category=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/category/domain/Category.java | 2 +- .../repository/CategoryRepository.java | 1 + .../category/service/CategoryService.java | 8 ++++- .../category/service/CategoryServiceTest.java | 35 +++++++++++++++---- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index 0c4aa47da..59c7ef843 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -18,7 +18,7 @@ public class Category extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false) + @Column(nullable = false, unique = true) private String name; public Category(String name) { diff --git a/backend/src/main/java/codezap/category/repository/CategoryRepository.java b/backend/src/main/java/codezap/category/repository/CategoryRepository.java index 8b3644a89..fe335048a 100644 --- a/backend/src/main/java/codezap/category/repository/CategoryRepository.java +++ b/backend/src/main/java/codezap/category/repository/CategoryRepository.java @@ -5,4 +5,5 @@ import codezap.category.domain.Category; public interface CategoryRepository extends JpaRepository { + boolean existsByName(String name); } diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 7dcb46b59..b1e52b55e 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -1,11 +1,13 @@ package codezap.category.service; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; import codezap.category.repository.CategoryRepository; +import codezap.global.exception.CodeZapException; @Service public class CategoryService { @@ -18,7 +20,11 @@ public CategoryService(CategoryRepository categoryRepository) { @Transactional public Long create(CreateCategoryRequest createCategoryRequest) { - Category category = new Category(createCategoryRequest.name()); + String categoryName = createCategoryRequest.name(); + if(categoryRepository.existsByName(categoryName)) { + throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재하고 있습니다."); + } + Category category = new Category(categoryName); return categoryRepository.save(category).getId(); } } diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index 1c98c92b7..167ab9b12 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -1,9 +1,11 @@ package codezap.category.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -12,7 +14,10 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.repository.CategoryRepository; +import codezap.global.exception.CodeZapException; import io.restassured.RestAssured; @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @@ -26,18 +31,36 @@ class CategoryServiceTest { @Autowired private CategoryService categoryService; + @Autowired + private CategoryRepository categoryRepository; + @BeforeEach void setting() { RestAssured.port = port; } - @Test - @DisplayName("카테고리 생성 성공") - void createCategorySuccess() { - CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category1"); + @Nested + @DisplayName("카테고리 생성 테스트") + class createCategoryTest { + @Test + @DisplayName("카테고리 생성 성공") + void createCategorySuccess() { + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category1"); + + Long categoryId = categoryService.create(createCategoryRequest); + + assertThat(categoryId).isEqualTo(1L); + } - Long categoryId = categoryService.create(createCategoryRequest); + @Test + @DisplayName("카테고리 생성 실패: 중복된 이름의 카테고리 이름 생성") + void createCategoryFailWithDuplicateName() { + categoryRepository.save(new Category("category")); + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category"); - assertThat(categoryId).isEqualTo(1L); + assertThatThrownBy(() -> categoryService.create(createCategoryRequest)) + .isInstanceOf(CodeZapException.class) + .hasMessage("이름이 " + createCategoryRequest.name() + "인 카테고리가 이미 존재하고 있습니다."); + } } } \ No newline at end of file From f23f7ae051079687e44277d1b2a3a93b1e3e62cd Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 21:49:42 +0900 Subject: [PATCH 008/168] =?UTF-8?q?feat(category):=20category=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 7 +++++++ .../response/FindAllCategoriesResponse.java | 17 +++++++++++++++ .../response/FindCategoryByIdResponse.java | 12 +++++++++++ .../category/service/CategoryService.java | 5 +++++ .../controller/CategoryControllerTest.java | 21 +++++++++++++++++++ .../category/service/CategoryServiceTest.java | 12 +++++++++++ 6 files changed, 74 insertions(+) create mode 100644 backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java create mode 100644 backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 6f546ef1e..27e50a673 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -5,12 +5,14 @@ import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.service.CategoryService; @RestController @@ -28,4 +30,9 @@ public ResponseEntity createCategory(@Valid @RequestBody CreateCategoryReq return ResponseEntity.created(URI.create("/categories/" + categoryService.create(createCategoryRequest))) .build(); } + + @GetMapping + public ResponseEntity getCategories() { + return ResponseEntity.ok(categoryService.findAll()); + } } diff --git a/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java b/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java new file mode 100644 index 000000000..e7df7c5a6 --- /dev/null +++ b/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java @@ -0,0 +1,17 @@ +package codezap.category.dto.response; + +import java.util.List; + +import codezap.category.domain.Category; + +public record FindAllCategoriesResponse( + List categories +) { + public static FindAllCategoriesResponse from(List categories) { + return new FindAllCategoriesResponse( + categories.stream() + .map(FindCategoryByIdResponse::from) + .toList() + ); + } +} diff --git a/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java b/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java new file mode 100644 index 000000000..fe7909aec --- /dev/null +++ b/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java @@ -0,0 +1,12 @@ +package codezap.category.dto.response; + +import codezap.category.domain.Category; + +public record FindCategoryByIdResponse( + Long id, + String name +) { + public static FindCategoryByIdResponse from(Category category) { + return new FindCategoryByIdResponse(category.getId(), category.getName()); + } +} diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index b1e52b55e..02a83af88 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -6,6 +6,7 @@ import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.repository.CategoryRepository; import codezap.global.exception.CodeZapException; @@ -27,4 +28,8 @@ public Long create(CreateCategoryRequest createCategoryRequest) { Category category = new Category(categoryName); return categoryRepository.save(category).getId(); } + + public FindAllCategoriesResponse findAll() { + return FindAllCategoriesResponse.from(categoryRepository.findAll()); + } } diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index edfbf41a2..16f519f83 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.server.LocalServerPort; @@ -13,6 +14,7 @@ import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.service.CategoryService; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -26,6 +28,9 @@ class CategoryControllerTest { @LocalServerPort int port; + @Autowired + private CategoryService categoryService; + @BeforeEach void setting() { RestAssured.port = port; @@ -62,4 +67,20 @@ void createCategoryFail() { .body("detail", is("카테고리 이름은 최대 255자까지 입력 가능합니다.")); } } + + @Test + @DisplayName("카테고리 전체 조회 성공") + void findAllCategoriesSuccess() { + CreateCategoryRequest createCategoryRequest1 = new CreateCategoryRequest("category1"); + CreateCategoryRequest createCategoryRequest2 = new CreateCategoryRequest("category2"); + categoryService.create(createCategoryRequest1); + categoryService.create(createCategoryRequest2); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .when().get("/categories") + .then().log().all() + .statusCode(200) + .body("categories.size()", is(2)); + } } \ No newline at end of file diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index 167ab9b12..944a184af 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -16,6 +16,7 @@ import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.repository.CategoryRepository; import codezap.global.exception.CodeZapException; import io.restassured.RestAssured; @@ -63,4 +64,15 @@ void createCategoryFailWithDuplicateName() { .hasMessage("이름이 " + createCategoryRequest.name() + "인 카테고리가 이미 존재하고 있습니다."); } } + + @Test + @DisplayName("카테고리 전체 조회 테스트") + void findAllCategoriesSuccess() { + categoryRepository.save(new Category("category1")); + categoryRepository.save(new Category("category2")); + + FindAllCategoriesResponse findAllCategoriesResponse = categoryService.findAll(); + + assertThat(findAllCategoriesResponse.categories()).hasSize(2); + } } \ No newline at end of file From 62de1ab1065d824e514bea7bb98caf1f5bd73063 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 21:51:45 +0900 Subject: [PATCH 009/168] =?UTF-8?q?refactor(domain):=20lombok=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B5=AC=ED=98=84=20=EC=88=9C=EC=84=9C?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/codezap/category/domain/Category.java | 2 +- backend/src/main/java/codezap/template/domain/Snippet.java | 2 +- backend/src/main/java/codezap/template/domain/Tag.java | 2 +- backend/src/main/java/codezap/template/domain/Template.java | 2 +- backend/src/main/java/codezap/template/domain/TemplateTag.java | 3 ++- .../main/java/codezap/template/domain/ThumbnailSnippet.java | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index 59c7ef843..f28666022 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -11,8 +11,8 @@ import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor +@Getter public class Category extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/backend/src/main/java/codezap/template/domain/Snippet.java b/backend/src/main/java/codezap/template/domain/Snippet.java index 8cf0291a5..c85b0ee36 100644 --- a/backend/src/main/java/codezap/template/domain/Snippet.java +++ b/backend/src/main/java/codezap/template/domain/Snippet.java @@ -16,8 +16,8 @@ import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor +@Getter public class Snippet extends BaseTimeEntity { private static final String CODE_LINE_BREAK = "\n"; diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java index 7859a3f61..0c2208952 100644 --- a/backend/src/main/java/codezap/template/domain/Tag.java +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -11,8 +11,8 @@ import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor +@Getter public class Tag extends BaseTimeEntity { @Id diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index a00b75b63..29e518068 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -13,8 +13,8 @@ import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor +@Getter public class Template extends BaseTimeEntity { @Id diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java index 7fa96742c..72729dbb4 100644 --- a/backend/src/main/java/codezap/template/domain/TemplateTag.java +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -12,10 +12,11 @@ import codezap.global.auditing.BaseTimeEntity; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; @Entity -@RequiredArgsConstructor +@NoArgsConstructor @Getter public class TemplateTag extends BaseTimeEntity { diff --git a/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java b/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java index 8c6b5feea..920dc18d2 100644 --- a/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java +++ b/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java @@ -11,8 +11,8 @@ import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor +@Getter public class ThumbnailSnippet extends BaseTimeEntity { @Id From aae4cae3cdf50aafb8607b8aa6eedadb962c7356 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Wed, 31 Jul 2024 13:26:34 +0900 Subject: [PATCH 010/168] =?UTF-8?q?refactor(domain):=20Tag=20value=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=A5=BC=20name=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/codezap/template/domain/Tag.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java index 0c2208952..8098b5d29 100644 --- a/backend/src/main/java/codezap/template/domain/Tag.java +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -20,5 +20,5 @@ public class Tag extends BaseTimeEntity { private Long id; @Column(nullable = false) - private String value; + private String name; } From 00070a2b6430e84c5880fb80f59549dd9ae002c7 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Wed, 31 Jul 2024 15:32:14 +0900 Subject: [PATCH 011/168] =?UTF-8?q?feat(codezap):=20template=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B8=B0=EB=8A=A5=EC=97=90=20tag=EC=99=80=20catego?= =?UTF-8?q?ry=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/category/domain/Category.java | 1 - .../repository/CategoryRepository.java | 8 ++++++ .../java/codezap/template/domain/Tag.java | 4 +++ .../codezap/template/domain/Template.java | 6 +++- .../codezap/template/domain/TemplateTag.java | 11 +++++++- .../dto/request/CreateTemplateRequest.java | 6 +++- .../template/repository/TagRepository.java | 8 ++++++ .../repository/TemplateTagRepository.java | 8 ++++++ .../template/service/TemplateService.java | 28 +++++++++++++++++-- 9 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 backend/src/main/java/codezap/template/repository/TagRepository.java create mode 100644 backend/src/main/java/codezap/template/repository/TemplateTagRepository.java diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index f28666022..5ffb71ed2 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -22,7 +22,6 @@ public class Category extends BaseTimeEntity { private String name; public Category(String name) { - this.id = null; this.name = name; } } diff --git a/backend/src/main/java/codezap/category/repository/CategoryRepository.java b/backend/src/main/java/codezap/category/repository/CategoryRepository.java index fe335048a..0f710a2c4 100644 --- a/backend/src/main/java/codezap/category/repository/CategoryRepository.java +++ b/backend/src/main/java/codezap/category/repository/CategoryRepository.java @@ -1,9 +1,17 @@ package codezap.category.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.http.HttpStatus; import codezap.category.domain.Category; +import codezap.global.exception.CodeZapException; public interface CategoryRepository extends JpaRepository { + + default Category fetchById(Long id) { + return findById(id).orElseThrow( + () -> new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + id + "에 해당하는 카테고리가 존재하지 않습니다.")); + } + boolean existsByName(String name); } diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java index 8098b5d29..eb7bf6fd7 100644 --- a/backend/src/main/java/codezap/template/domain/Tag.java +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -21,4 +21,8 @@ public class Tag extends BaseTimeEntity { @Column(nullable = false) private String name; + + public Tag(String name) { + this.name = name; + } } diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index 29e518068..bd55d4103 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -24,11 +24,15 @@ public class Template extends BaseTimeEntity { @Column(nullable = false) private String title; + @Column(columnDefinition = "TEXT") + private String description; + @ManyToOne(optional = false) private Category category; - public Template(String title) { + public Template(String title, Category category) { this.title = title; + this.category = category; } public void updateTitle(String title) { diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java index 72729dbb4..0d46698ee 100644 --- a/backend/src/main/java/codezap/template/domain/TemplateTag.java +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -10,10 +10,12 @@ import jakarta.persistence.MapsId; import codezap.global.auditing.BaseTimeEntity; +import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; +import lombok.Setter; @Entity @NoArgsConstructor @@ -21,7 +23,8 @@ public class TemplateTag extends BaseTimeEntity { @Embeddable - @RequiredArgsConstructor + @NoArgsConstructor + @AllArgsConstructor @Getter @EqualsAndHashCode private static class TemplateTagId implements Serializable { @@ -41,4 +44,10 @@ private static class TemplateTagId implements Serializable { @MapsId("tagId") @JoinColumn(name = "tag_id") private Tag tag; + + public TemplateTag(Template template, Tag tag) { + this.id = new TemplateTagId(template.getId(), tag.getId()); + this.template = template; + this.tag = tag; + } } \ No newline at end of file diff --git a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java index 9663ba500..9527ff657 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java @@ -18,7 +18,11 @@ public record CreateTemplateRequest( @Schema(description = "템플릿의 스니펫 내역") @NotNull(message = "스니펫 리스트가 null 입니다.") @Valid - List snippets + List snippets, + + Long categoryId, + + List tags ) implements ValidatedSnippetsOrdinalRequest { @Override public List extractSnippetsOrdinal() { diff --git a/backend/src/main/java/codezap/template/repository/TagRepository.java b/backend/src/main/java/codezap/template/repository/TagRepository.java new file mode 100644 index 000000000..e813662e8 --- /dev/null +++ b/backend/src/main/java/codezap/template/repository/TagRepository.java @@ -0,0 +1,8 @@ +package codezap.template.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import codezap.template.domain.Tag; + +public interface TagRepository extends JpaRepository { +} diff --git a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java new file mode 100644 index 000000000..5396f030c --- /dev/null +++ b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java @@ -0,0 +1,8 @@ +package codezap.template.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import codezap.template.domain.TemplateTag; + +public interface TemplateTagRepository extends JpaRepository { +} diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 57be444c7..8adb4f6aa 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -6,8 +6,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import codezap.category.domain.Category; +import codezap.category.repository.CategoryRepository; import codezap.template.domain.Snippet; +import codezap.template.domain.Tag; import codezap.template.domain.Template; +import codezap.template.domain.TemplateTag; import codezap.template.domain.ThumbnailSnippet; import codezap.template.dto.request.CreateSnippetRequest; import codezap.template.dto.request.CreateTemplateRequest; @@ -16,7 +20,9 @@ import codezap.template.dto.response.FindAllTemplatesResponse; import codezap.template.dto.response.FindTemplateByIdResponse; import codezap.template.repository.SnippetRepository; +import codezap.template.repository.TagRepository; import codezap.template.repository.TemplateRepository; +import codezap.template.repository.TemplateTagRepository; import codezap.template.repository.ThumbnailSnippetRepository; @Service @@ -27,19 +33,35 @@ public class TemplateService { private final ThumbnailSnippetRepository thumbnailSnippetRepository; private final TemplateRepository templateRepository; private final SnippetRepository snippetRepository; + private final CategoryRepository categoryRepository; + private final TagRepository tagRepository; + private final TemplateTagRepository templateTagRepository; public TemplateService(ThumbnailSnippetRepository thumbnailSnippetRepository, - TemplateRepository templateRepository, SnippetRepository snippetRepository + TemplateRepository templateRepository, SnippetRepository snippetRepository, + CategoryRepository categoryRepository, TagRepository tagRepository, + TemplateTagRepository templateTagRepository ) { this.thumbnailSnippetRepository = thumbnailSnippetRepository; this.templateRepository = templateRepository; this.snippetRepository = snippetRepository; + this.categoryRepository = categoryRepository; + this.tagRepository = tagRepository; + this.templateTagRepository = templateTagRepository; } @Transactional public Long create(CreateTemplateRequest createTemplateRequest) { - Template template = templateRepository.save( - new Template(createTemplateRequest.title())); + Category category = categoryRepository.fetchById(createTemplateRequest.categoryId()); + + Template template = templateRepository.save(new Template(createTemplateRequest.title(), category)); + + List tags = createTemplateRequest.tags().stream() + .map(Tag::new) + .map(tagRepository::save) + .toList(); + + tags.forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); createTemplateRequest.snippets() .forEach(createSnippetRequest -> createSnippet(createSnippetRequest, template)); From 7a3ed8f025972a7b647246146ea0007c8c944c37 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Wed, 31 Jul 2024 16:04:00 +0900 Subject: [PATCH 012/168] =?UTF-8?q?refactor(test):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TemplateControllerTest.java | 48 +++++++++++++++---- .../repository/SnippetRepositoryTest.java | 10 +++- .../template/service/TemplateServiceTest.java | 12 ++++- backend/src/test/resources/clear.sql | 3 +- 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 12833e4ef..b8659010f 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -17,6 +17,8 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.service.CategoryService; import codezap.template.dto.request.CreateSnippetRequest; import codezap.template.dto.request.CreateTemplateRequest; import codezap.template.dto.request.UpdateSnippetRequest; @@ -35,6 +37,9 @@ class TemplateControllerTest { @Autowired private TemplateService templateService; + @Autowired + private CategoryService categoryService; + @LocalServerPort int port; @@ -52,9 +57,13 @@ class createTemplateTest { @CsvSource({"a, 65535", "ㄱ, 21845"}) void createTemplateSuccess(String repeatTarget, int maxLength) { String maxTitle = "a".repeat(MAX_LENGTH); + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest(maxTitle, - List.of(new CreateSnippetRequest("a".repeat(MAX_LENGTH), repeatTarget.repeat(maxLength), 1))); - + List.of(new CreateSnippetRequest("a".repeat(MAX_LENGTH), repeatTarget.repeat(maxLength), 1)), + 1L, + List.of("tag1", "tag2") + ); + RestAssured.given().log().all() .contentType(ContentType.JSON) .body(templateRequest) @@ -68,9 +77,13 @@ void createTemplateSuccess(String repeatTarget, int maxLength) { @DisplayName("템플릿 생성 실패: 템플릿 이름 길이 초과") void createTemplateFailWithLongTitle() { String exceededTitle = "a".repeat(MAX_LENGTH + 1); + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest(exceededTitle, - List.of(new CreateSnippetRequest("a", "content", 1))); - + List.of(new CreateSnippetRequest("a", "content", 1)), + 1L, + List.of("tag1", "tag2") + ); + RestAssured.given().log().all() .contentType(ContentType.JSON) .body(templateRequest) @@ -84,8 +97,12 @@ void createTemplateFailWithLongTitle() { @DisplayName("템플릿 생성 실패: 파일 이름 길이 초과") void createTemplateFailWithLongFileName() { String exceededTitle = "a".repeat(MAX_LENGTH + 1); + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest("title", - List.of(new CreateSnippetRequest(exceededTitle, "content", 1))); + List.of(new CreateSnippetRequest(exceededTitle, "content", 1)), + 1L, + List.of("tag1", "tag2") + ); RestAssured.given().log().all() .contentType(ContentType.JSON) @@ -100,8 +117,12 @@ void createTemplateFailWithLongFileName() { @DisplayName("템플릿 생성 실패: 파일 내용 길이 초과") @CsvSource({"a, 65536", "ㄱ, 21846"}) void createTemplateFailWithLongContent(String repeatTarget, int exceededLength) { + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest("title", - List.of(new CreateSnippetRequest("title", repeatTarget.repeat(exceededLength), 1))); + List.of(new CreateSnippetRequest("title", repeatTarget.repeat(exceededLength), 1)), + 1L, + List.of("tag1", "tag2") + ); RestAssured.given().log().all() .contentType(ContentType.JSON) @@ -116,9 +137,13 @@ void createTemplateFailWithLongContent(String repeatTarget, int exceededLength) @DisplayName("템플릿 생성 실패: 잘못된 스니펫 순서 입력") @CsvSource({"0, 1", "1, 3", "2, 1"}) void createTemplateFailWithWrongSnippetOrdinal(int firstIndex, int secondIndex) { + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest("title", List.of(new CreateSnippetRequest("title", "content", firstIndex), - new CreateSnippetRequest("title", "content", secondIndex))); + new CreateSnippetRequest("title", "content", secondIndex)), + 1L, + List.of("tag1", "tag2") + ); RestAssured.given().log().all() .contentType(ContentType.JSON) @@ -136,6 +161,7 @@ void findAllTemplatesSuccess() { // given CreateTemplateRequest templateRequest1 = createTemplateRequestWithTwoSnippets("title1"); CreateTemplateRequest templateRequest2 = createTemplateRequestWithTwoSnippets("title2"); + categoryService.create(new CreateCategoryRequest("category")); templateService.create(templateRequest1); templateService.create(templateRequest2); @@ -155,6 +181,7 @@ class findTemplateTest { @DisplayName("템플릿 상세 조회 성공") void findOneTemplateSuccess() { // given + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -187,6 +214,7 @@ class updateTemplateTest { @DisplayName("템플릿 수정 성공") void updateTemplateSuccess() { // given + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -217,6 +245,7 @@ void updateTemplateSuccess() { @CsvSource({"1, 2, 1", "3, 2, 1", "0, 2, 1"}) void updateTemplateFailWithWrongSnippetOrdinal(int createOrdinal1, int createOrdinal2, int updateOrdinal) { // given + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -251,6 +280,7 @@ class deleteTemplateTest { @DisplayName("템플릿 삭제 성공") void deleteTemplateSuccess() { // given + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -279,7 +309,9 @@ private static CreateTemplateRequest createTemplateRequestWithTwoSnippets(String List.of( new CreateSnippetRequest("filename1", "content1", 1), new CreateSnippetRequest("filename2", "content2", 2) - ) + ), + 1L, + List.of("tag1", "tag2") ); return templateRequest; } diff --git a/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java b/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java index 750ce4055..075ee12c7 100644 --- a/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java +++ b/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java @@ -13,6 +13,8 @@ import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.transaction.annotation.Transactional; +import codezap.category.domain.Category; +import codezap.category.repository.CategoryRepository; import codezap.template.domain.Snippet; import codezap.template.domain.Template; @@ -26,11 +28,14 @@ class SnippetRepositoryTest { private SnippetRepository snippetRepository; @Autowired private TemplateRepository templateRepository; + @Autowired + private CategoryRepository categoryRepository; @Test @DisplayName("단일 스니펫 찾기 성공: 템플릿과 순서") void findOneSnippetSuccessWithTemplateAndOrdinal() { - Template template = templateRepository.save(new Template("title")); + Category category = categoryRepository.save(new Category("category")); + Template template = templateRepository.save(new Template("title", category)); Snippet snippet1 = snippetRepository.save(new Snippet(template, "filename1", "content1", 1)); Snippet snippet2 = snippetRepository.save(new Snippet(template, "filename2", "content2", 2)); @@ -47,7 +52,8 @@ void findOneSnippetSuccessWithTemplateAndOrdinal() { @Test @DisplayName("스니펫 리스트 찾기 성공: 템플릿과 순서") void findSnippetsSuccessWithTemplateAndOrdinal() { - Template template = templateRepository.save(new Template("title")); + Category category = categoryRepository.save(new Category("category")); + Template template = templateRepository.save(new Template("title", category)); Snippet snippet1 = snippetRepository.save(new Snippet(template, "filename1", "content1", 1)); Snippet snippet2 = snippetRepository.save(new Snippet(template, "filename2", "content2", 2)); Snippet snippet3 = snippetRepository.save(new Snippet(template, "filename3", "content3", 2)); diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index fea66f4c4..27cb2591d 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -15,6 +15,8 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import codezap.category.domain.Category; +import codezap.category.repository.CategoryRepository; import codezap.template.domain.Snippet; import codezap.template.domain.Template; import codezap.template.domain.ThumbnailSnippet; @@ -48,6 +50,8 @@ class TemplateServiceTest { @Autowired private ThumbnailSnippetRepository thumbnailSnippetRepository; + @Autowired + private CategoryRepository categoryRepository; @BeforeEach void setting() { @@ -59,6 +63,7 @@ void setting() { void createTemplateSuccess() { // given CreateTemplateRequest createTemplateRequest = makeTemplateRequest("title"); + categoryRepository.save(new Category("category")); // when templateService.create(createTemplateRequest); @@ -143,7 +148,9 @@ private CreateTemplateRequest makeTemplateRequest(String title) { List.of( new CreateSnippetRequest("filename1", "content1", 1), new CreateSnippetRequest("filename2", "content2", 2) - ) + ), + 1L, + List.of("tag1", "tag2") ); } @@ -162,7 +169,8 @@ private UpdateTemplateRequest makeUpdateTemplateRequest(String title) { } private Template saveTemplate(CreateTemplateRequest createTemplateRequest) { - Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title())); + Category category = categoryRepository.save(new Category("category")); + Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title(), category)); Snippet savedFirstSnippet = snippetRepository.save(new Snippet(savedTemplate, "filename1", "content1", 1)); snippetRepository.save(new Snippet(savedTemplate, "filename2", "content2", 2)); thumbnailSnippetRepository.save(new ThumbnailSnippet(savedTemplate, savedFirstSnippet)); diff --git a/backend/src/test/resources/clear.sql b/backend/src/test/resources/clear.sql index 8cb83de09..9aada0497 100644 --- a/backend/src/test/resources/clear.sql +++ b/backend/src/test/resources/clear.sql @@ -18,6 +18,7 @@ CREATE TABLE template ( id BIGINT NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL, + description TEXT, category_id BIGINT NOT NULL, created_at DATETIME(6) NOT NULL, modified_at DATETIME(6) NOT NULL, @@ -28,7 +29,7 @@ CREATE TABLE template create table tag ( id BIGINT NOT NULL auto_increment, - value VARCHAR(255) NOT NULL, + name VARCHAR(255) NOT NULL, created_at DATETIME(6) NOT NULL, modified_at DATETIME(6) NOT NULL, PRIMARY KEY (id) From 100d7ecee9f73d160f189eb149edf10a5ae3fbac Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Wed, 31 Jul 2024 16:32:18 +0900 Subject: [PATCH 013/168] =?UTF-8?q?feat(test):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=EC=97=90=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC,=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/service/CategoryService.java | 2 +- .../codezap/template/domain/TemplateTag.java | 2 -- .../dto/response/FindTagByIdResponse.java | 12 +++++++++++ .../response/FindTemplateByIdResponse.java | 21 ++++++++++++++++++- .../repository/TemplateTagRepository.java | 5 +++++ .../template/service/TemplateService.java | 5 ++++- .../controller/TemplateControllerTest.java | 5 ++++- .../template/service/TemplateServiceTest.java | 19 ++++++++++++++++- 8 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 02a83af88..e4c59e5de 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -22,7 +22,7 @@ public CategoryService(CategoryRepository categoryRepository) { @Transactional public Long create(CreateCategoryRequest createCategoryRequest) { String categoryName = createCategoryRequest.name(); - if(categoryRepository.existsByName(categoryName)) { + if (categoryRepository.existsByName(categoryName)) { throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재하고 있습니다."); } Category category = new Category(categoryName); diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java index 0d46698ee..95214758d 100644 --- a/backend/src/main/java/codezap/template/domain/TemplateTag.java +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -14,8 +14,6 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; -import lombok.Setter; @Entity @NoArgsConstructor diff --git a/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java b/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java new file mode 100644 index 000000000..8052780b0 --- /dev/null +++ b/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java @@ -0,0 +1,12 @@ +package codezap.template.dto.response; + +import codezap.template.domain.Tag; + +public record FindTagByIdResponse( + Long id, + String name +) { + public static FindTagByIdResponse from(Tag tag) { + return new FindTagByIdResponse(tag.getId(), tag.getName()); + } +} diff --git a/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java b/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java index 6fccceb7c..734741750 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java @@ -3,25 +3,36 @@ import java.time.LocalDateTime; import java.util.List; +import codezap.category.dto.response.FindCategoryByIdResponse; import codezap.template.domain.Snippet; +import codezap.template.domain.Tag; import codezap.template.domain.Template; import io.swagger.v3.oas.annotations.media.Schema; public record FindTemplateByIdResponse( @Schema(description = "템플릿 식별자", example = "0") Long id, + @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") String title, + @Schema(description = "스니펫 목록") List snippets, + + FindCategoryByIdResponse category, + + List tags, + @Schema(description = "템플릿 수정 시간", example = "2024-11-11 12:00", type = "string") LocalDateTime modifiedAt ) { - public static FindTemplateByIdResponse of(Template template, List snippets) { + public static FindTemplateByIdResponse of(Template template, List snippets, List tags) { return new FindTemplateByIdResponse( template.getId(), template.getTitle(), mapToFindAllSnippetByTemplateResponse(snippets), + FindCategoryByIdResponse.from(template.getCategory()), + mapToFindTagByTemplateResponse(tags), template.getModifiedAt() ); } @@ -33,4 +44,12 @@ private static List mapToFindAllSnippetByTempl .map(FindAllSnippetByTemplateResponse::from) .toList(); } + + private static List mapToFindTagByTemplateResponse( + List tags + ) { + return tags.stream() + .map(FindTagByIdResponse::from) + .toList(); + } } diff --git a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java index 5396f030c..b49d7355f 100644 --- a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java @@ -1,8 +1,13 @@ package codezap.template.repository; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; +import codezap.template.domain.Template; import codezap.template.domain.TemplateTag; public interface TemplateTagRepository extends JpaRepository { + + List findAllByTemplate(Template template); } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 8adb4f6aa..538ff606d 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -78,7 +78,10 @@ public FindAllTemplatesResponse findAll() { public FindTemplateByIdResponse findById(Long id) { Template template = templateRepository.fetchById(id); List snippets = snippetRepository.findAllByTemplate(template); - return FindTemplateByIdResponse.of(template, snippets); + List tags = templateTagRepository.findAllByTemplate(template).stream() + .map(TemplateTag::getTag) + .toList(); + return FindTemplateByIdResponse.of(template, snippets, tags); } @Transactional diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index b8659010f..47d11ab65 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -191,7 +191,10 @@ void findOneTemplateSuccess() { .then().log().all() .statusCode(200) .body("title", is(templateRequest.title()), - "snippets.size()", is(2)); + "snippets.size()", is(2), + "category.id", is(1), + "category.name", is("category"), + "tags.size()", is(2)); } @Test diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 27cb2591d..10d97d771 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -18,7 +18,9 @@ import codezap.category.domain.Category; import codezap.category.repository.CategoryRepository; import codezap.template.domain.Snippet; +import codezap.template.domain.Tag; import codezap.template.domain.Template; +import codezap.template.domain.TemplateTag; import codezap.template.domain.ThumbnailSnippet; import codezap.template.dto.request.CreateSnippetRequest; import codezap.template.dto.request.CreateTemplateRequest; @@ -27,7 +29,9 @@ import codezap.template.dto.response.FindAllTemplatesResponse; import codezap.template.dto.response.FindTemplateByIdResponse; import codezap.template.repository.SnippetRepository; +import codezap.template.repository.TagRepository; import codezap.template.repository.TemplateRepository; +import codezap.template.repository.TemplateTagRepository; import codezap.template.repository.ThumbnailSnippetRepository; import io.restassured.RestAssured; @@ -50,9 +54,15 @@ class TemplateServiceTest { @Autowired private ThumbnailSnippetRepository thumbnailSnippetRepository; + @Autowired private CategoryRepository categoryRepository; + @Autowired + private TemplateTagRepository templateTagRepository; + @Autowired + private TagRepository tagRepository; + @BeforeEach void setting() { RestAssured.port = port; @@ -99,7 +109,10 @@ void findOneTemplateSuccess() { // then assertAll( () -> assertThat(foundTemplate.title()).isEqualTo(template.getTitle()), - () -> assertThat(foundTemplate.snippets()).hasSize(snippetRepository.findAllByTemplate(template).size()) + () -> assertThat(foundTemplate.snippets()).hasSize( + snippetRepository.findAllByTemplate(template).size()), + () -> assertThat(foundTemplate.category().id()).isEqualTo(template.getCategory().getId()), + () -> assertThat(foundTemplate.tags()).hasSize(2) ); } @@ -174,6 +187,10 @@ private Template saveTemplate(CreateTemplateRequest createTemplateRequest) { Snippet savedFirstSnippet = snippetRepository.save(new Snippet(savedTemplate, "filename1", "content1", 1)); snippetRepository.save(new Snippet(savedTemplate, "filename2", "content2", 2)); thumbnailSnippetRepository.save(new ThumbnailSnippet(savedTemplate, savedFirstSnippet)); + createTemplateRequest.tags().stream() + .map(Tag::new) + .map(tagRepository::save) + .forEach(tag -> templateTagRepository.save(new TemplateTag(savedTemplate, tag))); return savedTemplate; } From 22bbb482fd0f478a5ac40a6569a976485ca9c18d Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Thu, 1 Aug 2024 11:28:56 +0900 Subject: [PATCH 014/168] =?UTF-8?q?feat(template):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=EC=97=90=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=B0=8F=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/template/domain/Template.java | 3 ++- .../codezap/template/domain/TemplateTag.java | 2 +- .../dto/request/UpdateTemplateRequest.java | 6 ++++- .../template/repository/TagRepository.java | 3 +++ .../repository/TemplateTagRepository.java | 2 ++ .../template/service/TemplateService.java | 14 ++++++++++- .../controller/TemplateControllerTest.java | 14 ++++++++--- .../template/service/TemplateServiceTest.java | 25 +++++++++++++++---- 8 files changed, 56 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index bd55d4103..5bdfa6446 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -35,7 +35,8 @@ public Template(String title, Category category) { this.category = category; } - public void updateTitle(String title) { + public void updateTitle(String title, Category category) { this.title = title; + this.category = category; } } diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java index 95214758d..eefb2ca0b 100644 --- a/backend/src/main/java/codezap/template/domain/TemplateTag.java +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -48,4 +48,4 @@ public TemplateTag(Template template, Tag tag) { this.template = template; this.tag = tag; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java index 06e2cfcaf..16fd59c91 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java @@ -23,7 +23,11 @@ public record UpdateTemplateRequest( @Schema(description = "삭제한 스니펫 식별자") @NotNull(message = "deleteSnippetIds 리스트가 null 입니다.") - List deleteSnippetIds + List deleteSnippetIds, + + Long categoryId, + + List tags ) implements ValidatedSnippetsOrdinalRequest { @Override public List extractSnippetsOrdinal() { diff --git a/backend/src/main/java/codezap/template/repository/TagRepository.java b/backend/src/main/java/codezap/template/repository/TagRepository.java index e813662e8..612dee2b3 100644 --- a/backend/src/main/java/codezap/template/repository/TagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TagRepository.java @@ -5,4 +5,7 @@ import codezap.template.domain.Tag; public interface TagRepository extends JpaRepository { + boolean existsByName(String name); + + Tag findByName(String name); } diff --git a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java index b49d7355f..6b0ab4f6e 100644 --- a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java @@ -10,4 +10,6 @@ public interface TemplateTagRepository extends JpaRepository { List findAllByTemplate(Template template); + + void deleteAllByTemplate(Template template); } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 538ff606d..b610bed92 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -86,8 +86,9 @@ public FindTemplateByIdResponse findById(Long id) { @Transactional public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) { + Category category = categoryRepository.fetchById(updateTemplateRequest.categoryId()); Template template = templateRepository.fetchById(templateId); - template.updateTitle(updateTemplateRequest.title()); + template.updateTitle(updateTemplateRequest.title(), category); updateTemplateRequest.updateSnippets().forEach(this::updateSnippet); updateTemplateRequest.createSnippets() @@ -100,6 +101,17 @@ public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) } updateTemplateRequest.deleteSnippetIds().forEach(snippetRepository::deleteById); + + templateTagRepository.deleteAllByTemplate(template); + updateTemplateRequest.tags().stream() + .map(Tag::new) + .filter(tag -> !tagRepository.existsByName(tag.getName())) + .forEach(tagRepository::save); + + List tags = updateTemplateRequest.tags().stream() + .map(tagRepository::findByName) + .toList(); + tags.forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); } @Transactional diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 47d11ab65..c45ac1c18 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -217,7 +217,8 @@ class updateTemplateTest { @DisplayName("템플릿 수정 성공") void updateTemplateSuccess() { // given - categoryService.create(new CreateCategoryRequest("category")); + categoryService.create(new CreateCategoryRequest("category1")); + categoryService.create(new CreateCategoryRequest("category2")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -230,7 +231,9 @@ void updateTemplateSuccess() { List.of( new UpdateSnippetRequest(2L, "updateFilename2", "updateContent2", 1) ), - List.of(1L) + List.of(1L), + 2L, + List.of("tag1", "tag3") ); // when & then @@ -248,7 +251,8 @@ void updateTemplateSuccess() { @CsvSource({"1, 2, 1", "3, 2, 1", "0, 2, 1"}) void updateTemplateFailWithWrongSnippetOrdinal(int createOrdinal1, int createOrdinal2, int updateOrdinal) { // given - categoryService.create(new CreateCategoryRequest("category")); + categoryService.create(new CreateCategoryRequest("category1")); + categoryService.create(new CreateCategoryRequest("category2")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -261,7 +265,9 @@ void updateTemplateFailWithWrongSnippetOrdinal(int createOrdinal1, int createOrd List.of( new UpdateSnippetRequest(2L, "updateFilename2", "updateContent2", updateOrdinal) ), - List.of(1L) + List.of(1L), + 2L, + List.of("tag1", "tag3") ); // when & then diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 10d97d771..ba802e6b6 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -76,10 +76,15 @@ void createTemplateSuccess() { categoryRepository.save(new Category("category")); // when - templateService.create(createTemplateRequest); + Long id = templateService.create(createTemplateRequest); + Template template = templateRepository.fetchById(id); // then - assertThat(templateRepository.findAll()).hasSize(1); + assertAll( + () -> assertThat(templateRepository.findAll()).hasSize(1), + () -> assertThat(template.getTitle()).isEqualTo(createTemplateRequest.title()), + () -> assertThat(template.getCategory().getName()).isEqualTo("category") + ); } @Test @@ -122,18 +127,26 @@ void updateTemplateSuccess() { // given CreateTemplateRequest createdTemplate = makeTemplateRequest("title"); Template template = saveTemplate(createdTemplate); + categoryRepository.save(new Category("category2")); // when UpdateTemplateRequest updateTemplateRequest = makeUpdateTemplateRequest("updateTitle"); templateService.update(template.getId(), updateTemplateRequest); + Template updateTemplate = templateRepository.fetchById(template.getId()); List snippets = snippetRepository.findAllByTemplate(template); ThumbnailSnippet thumbnailSnippet = thumbnailSnippetRepository.findById(template.getId()).get(); + List tags = templateTagRepository.findAllByTemplate(updateTemplate).stream() + .map(TemplateTag::getTag) + .toList(); // then assertAll( - () -> assertThat(updateTemplateRequest.title()).isEqualTo("updateTitle"), + () -> assertThat(updateTemplate.getTitle()).isEqualTo("updateTitle"), () -> assertThat(thumbnailSnippet.getSnippet().getId()).isEqualTo(2L), - () -> assertThat(snippets).hasSize(3) + () -> assertThat(snippets).hasSize(3), + () -> assertThat(updateTemplate.getCategory().getId()).isEqualTo(2L), + () -> assertThat(tags).hasSize(2), + () -> assertThat(tags.get(1).getName()).isEqualTo("tag3") ); } @@ -177,7 +190,9 @@ private UpdateTemplateRequest makeUpdateTemplateRequest(String title) { List.of( new UpdateSnippetRequest(2L, "filename2", "content2", 1) ), - List.of(1L) + List.of(1L), + 2L, + List.of("tag1", "tag3") ); } From d8d40bc3cf77a7199d00eed5b5744861b027fc36 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Thu, 1 Aug 2024 11:35:46 +0900 Subject: [PATCH 015/168] =?UTF-8?q?feat(template):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codezap/template/repository/TemplateTagRepository.java | 2 ++ .../src/main/java/codezap/template/service/TemplateService.java | 1 + 2 files changed, 3 insertions(+) diff --git a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java index 6b0ab4f6e..41fe972d3 100644 --- a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java @@ -12,4 +12,6 @@ public interface TemplateTagRepository extends JpaRepository List findAllByTemplate(Template template); void deleteAllByTemplate(Template template); + + void deleteAllByTemplateId(Long id); } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index b610bed92..e6f185b08 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -118,6 +118,7 @@ public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) public void deleteById(Long id) { thumbnailSnippetRepository.deleteByTemplateId(id); snippetRepository.deleteByTemplateId(id); + templateTagRepository.deleteAllByTemplateId(id); templateRepository.deleteById(id); } From 457a8560f2e76c1dc04e4b8ee20dfca9446abbce Mon Sep 17 00:00:00 2001 From: zangsu Date: Thu, 1 Aug 2024 13:10:02 +0900 Subject: [PATCH 016/168] =?UTF-8?q?docs(category):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20swagger=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 17 ++++++- .../SpringDocCategoryController.java | 49 +++++++++++++++++++ .../dto/request/CreateCategoryRequest.java | 3 ++ .../dto/request/UpdateCategoryRequest.java | 17 +++++++ .../response/FindAllCategoriesResponse.java | 2 + .../response/FindCategoryByIdResponse.java | 3 ++ 6 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java create mode 100644 backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 27e50a673..2acc801b4 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -5,19 +5,23 @@ import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.service.CategoryService; @RestController @RequestMapping("/categories") -public class CategoryController { +public class CategoryController implements SpringDocCategoryController{ private final CategoryService categoryService; @@ -35,4 +39,15 @@ public ResponseEntity createCategory(@Valid @RequestBody CreateCategoryReq public ResponseEntity getCategories() { return ResponseEntity.ok(categoryService.findAll()); } + + @PutMapping("/{id}") + public ResponseEntity updateCategory(@PathVariable Long id, + @Valid @RequestBody UpdateCategoryRequest updateCategoryRequest) { + return null; + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteCategory(@PathVariable Long id) { + return null; + } } diff --git a/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java b/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java new file mode 100644 index 000000000..621d23002 --- /dev/null +++ b/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java @@ -0,0 +1,49 @@ +package codezap.category.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.request.UpdateCategoryRequest; +import codezap.category.dto.response.FindAllCategoriesResponse; +import codezap.global.swagger.error.ApiErrorResponse; +import codezap.global.swagger.error.ErrorCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "카테고리 CRUD API", description = "카테고리 생성, 목록 조회, 삭제, 수정 API") +public interface SpringDocCategoryController { + + @Operation(summary = "카테고리 생성", description = """ + 새로운 카테고리를 생성합니다. \n + 새로운 카테고리의 이름이 필요합니다. \n + """) + @ApiResponse(responseCode = "201", description = "카테고리 생성 성공", headers = { + @Header(name = "생성된 카테고리의 API 경로", example = "/categories/1")}) + @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories", errorCases = { + @ErrorCase(description = "모든 필드 중 null인 값이 있는 경우", exampleMessage = "카테고리 이름이 null 입니다."), + @ErrorCase(description = "카테고리 이름이 255자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 255자까지 입력 가능합니다.") + }) + ResponseEntity createCategory(CreateCategoryRequest createCategoryRequest); + + @Operation(summary = "카테고리 목록 조회", description = "생성된 모든 카테고리를 조회합니다.") + @ApiResponse(responseCode = "200", description = "조회 성공", + content = {@Content(schema = @Schema(implementation = FindAllCategoriesResponse.class))}) + ResponseEntity getCategories(); + + @Operation(summary = "카테고리 수정", description = "해당하는 식별자의 카테고리를 수정합니다.") + @ApiResponse(responseCode = "200", description = "카테고리 수정 성공") + @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories/1", errorCases = { + @ErrorCase(description = "해당하는 id 값인 카테고리가 없는 경우", + exampleMessage = "식별자 1에 해당하는 카테고리가 존재하지 않습니다."), + }) + ResponseEntity updateCategory(Long id, UpdateCategoryRequest updateCategoryRequest); + + @Operation(summary = "카테고리 삭제", description = "해당하는 식별자의 카테고리를 삭제합니다.") + @ApiResponse(responseCode = "204", description = "카테고리 삭제 성공") + ResponseEntity deleteCategory(Long id); +} diff --git a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java index 7d1276e94..86946b8a8 100644 --- a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java +++ b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java @@ -3,7 +3,10 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import io.swagger.v3.oas.annotations.media.Schema; + public record CreateCategoryRequest( + @Schema(description = "카테고리 이름", example = "Spring") @NotNull(message = "카테고리 이름이 null 입니다.") @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.") String name diff --git a/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java new file mode 100644 index 000000000..5a8bfb467 --- /dev/null +++ b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java @@ -0,0 +1,17 @@ +package codezap.category.dto.request; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record UpdateCategoryRequest( + @Schema(description = "카테고리 식별자", example = "1") + @NotNull(message = "카테고리 id가 null 입니다.") + Long id, + @Schema(description = "카테고리 이름", example = "Spring") + @NotNull(message = "카테고리 이름이 null 입니다.") + @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.") + String name +) { +} diff --git a/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java b/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java index e7df7c5a6..b17e2168f 100644 --- a/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java +++ b/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java @@ -3,8 +3,10 @@ import java.util.List; import codezap.category.domain.Category; +import io.swagger.v3.oas.annotations.media.Schema; public record FindAllCategoriesResponse( + @Schema(description = "카테고리 목록") List categories ) { public static FindAllCategoriesResponse from(List categories) { diff --git a/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java b/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java index fe7909aec..999a7a46a 100644 --- a/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java +++ b/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java @@ -1,9 +1,12 @@ package codezap.category.dto.response; import codezap.category.domain.Category; +import io.swagger.v3.oas.annotations.media.Schema; public record FindCategoryByIdResponse( + @Schema(description = "카테고리 식별자", example = "1") Long id, + @Schema(description = "카테고리 이름", example = "Spring") String name ) { public static FindCategoryByIdResponse from(Category category) { From f11917b9a32998e73381f5a569f1b722889eeb5e Mon Sep 17 00:00:00 2001 From: zangsu Date: Thu, 1 Aug 2024 18:29:20 +0900 Subject: [PATCH 017/168] =?UTF-8?q?docs(category):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 3 +- .../codezap/category/domain/Category.java | 4 ++ .../dto/request/UpdateCategoryRequest.java | 3 - .../category/service/CategoryService.java | 11 ++++ .../controller/CategoryControllerTest.java | 61 ++++++++++++++++++- .../category/service/CategoryServiceTest.java | 16 ++++- 6 files changed, 92 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 2acc801b4..311ed556f 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -43,7 +43,8 @@ public ResponseEntity getCategories() { @PutMapping("/{id}") public ResponseEntity updateCategory(@PathVariable Long id, @Valid @RequestBody UpdateCategoryRequest updateCategoryRequest) { - return null; + categoryService.update(id, updateCategoryRequest); + return ResponseEntity.ok().build(); } @DeleteMapping("/{id}") diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index 5ffb71ed2..b44c07ddb 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -24,4 +24,8 @@ public class Category extends BaseTimeEntity { public Category(String name) { this.name = name; } + + public void updateName(String name) { + this.name = name; + } } diff --git a/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java index 5a8bfb467..4ed48bf0c 100644 --- a/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java +++ b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java @@ -6,9 +6,6 @@ import io.swagger.v3.oas.annotations.media.Schema; public record UpdateCategoryRequest( - @Schema(description = "카테고리 식별자", example = "1") - @NotNull(message = "카테고리 id가 null 입니다.") - Long id, @Schema(description = "카테고리 이름", example = "Spring") @NotNull(message = "카테고리 이름이 null 입니다.") @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.") diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index e4c59e5de..151a63945 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -6,6 +6,7 @@ import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.repository.CategoryRepository; import codezap.global.exception.CodeZapException; @@ -32,4 +33,14 @@ public Long create(CreateCategoryRequest createCategoryRequest) { public FindAllCategoriesResponse findAll() { return FindAllCategoriesResponse.from(categoryRepository.findAll()); } + + @Transactional + public void update(Long id, UpdateCategoryRequest updateCategoryRequest) { + if (categoryRepository.existsByName(updateCategoryRequest.name())) { + throw new CodeZapException(HttpStatus.CONFLICT, + "이름이 " + updateCategoryRequest.name() + "인 카테고리가 이미 존재하고 있습니다."); + } + Category category = categoryRepository.fetchById(id); + category.updateName(updateCategoryRequest.name()); + } } diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index 16f519f83..18f66462d 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -14,6 +14,7 @@ import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.service.CategoryService; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -83,4 +84,62 @@ void findAllCategoriesSuccess() { .statusCode(200) .body("categories.size()", is(2)); } -} \ No newline at end of file + + @Nested + @DisplayName("카테고리 수정 테스트") + class updateCategoryTest { + + Long savedCategoryId; + + @BeforeEach + void saveCategory() { + savedCategoryId = categoryService.create(new CreateCategoryRequest("category1")); + } + + @Test + @DisplayName("카테고리 수정 성공") + void updateCategorySuccess() { + UpdateCategoryRequest updateCategoryRequest = new UpdateCategoryRequest("a".repeat(MAX_LENGTH)); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(updateCategoryRequest) + .when().put("/categories/" + savedCategoryId) + .then().log().all() + .statusCode(200); + } + + @Test + @DisplayName("카테고리 수정 실패: 카테고리 이름 길이 초과") + void updateCategoryFailWithLongName() { + UpdateCategoryRequest updateCategoryRequest = new UpdateCategoryRequest("a".repeat(MAX_LENGTH + 1)); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(updateCategoryRequest) + .when().put("/categories/" + savedCategoryId) + .then().log().all() + .statusCode(400) + .body("detail", is("카테고리 이름은 최대 255자까지 입력 가능합니다.")); + } + + @Test + @DisplayName("카테고리 수정 실패: 중복된 이름의 카테고리 존재") + void updateCategoryFailWithDuplicatedName() { + //given + String duplicatedName = "duplicatedName"; + categoryService.create(new CreateCategoryRequest(duplicatedName)); + + UpdateCategoryRequest createCategoryRequest = new UpdateCategoryRequest(duplicatedName); + + //when & then + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(createCategoryRequest) + .when().put("/categories/" + savedCategoryId) + .then().log().all() + .statusCode(409) + .body("detail", is("이름이 " + duplicatedName + "인 카테고리가 이미 존재하고 있습니다.")); + } + } +} diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index 944a184af..6dab5569f 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -16,6 +16,7 @@ import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.repository.CategoryRepository; import codezap.global.exception.CodeZapException; @@ -75,4 +76,17 @@ void findAllCategoriesSuccess() { assertThat(findAllCategoriesResponse.categories()).hasSize(2); } -} \ No newline at end of file + + @Test + @DisplayName("카테고리 수정 성공") + void updateCategorySuccess() { + //given + Category savedCategory = categoryRepository.save(new Category("category1")); + + //when + categoryService.update(savedCategory.getId(), new UpdateCategoryRequest("updateName")); + + //then + assertThat(categoryRepository.fetchById(savedCategory.getId()).getName()).isEqualTo("updateName"); + } +} From c4ff2645d4fe8e37f51446343281a2384b15d5f6 Mon Sep 17 00:00:00 2001 From: zangsu Date: Thu, 1 Aug 2024 18:30:00 +0900 Subject: [PATCH 018/168] =?UTF-8?q?refactor(controller):=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EB=B0=8F=20DisplayName=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/category/controller/CategoryControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index 18f66462d..b80149a6e 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -55,8 +55,8 @@ void createCategorySuccess() { } @Test - @DisplayName("카테고리 생성 실패:") - void createCategoryFail() { + @DisplayName("카테고리 생성 실패: 카테고리 이름 길이 초과") + void createCategoryFailWithLongName() { CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("a".repeat(MAX_LENGTH + 1)); RestAssured.given().log().all() From 13a537cf3e9ae292300dc3a727506e7425b2e54f Mon Sep 17 00:00:00 2001 From: zangsu Date: Thu, 1 Aug 2024 20:55:24 +0900 Subject: [PATCH 019/168] =?UTF-8?q?feat(category):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 4 +- .../category/service/CategoryService.java | 27 ++++++--- .../repository/TemplateRepository.java | 2 + .../controller/CategoryControllerTest.java | 57 +++++++++++++++++++ .../category/service/CategoryServiceTest.java | 13 +++++ 5 files changed, 94 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 311ed556f..ee6179d34 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -49,6 +49,8 @@ public ResponseEntity updateCategory(@PathVariable Long id, @DeleteMapping("/{id}") public ResponseEntity deleteCategory(@PathVariable Long id) { - return null; + categoryService.deleteById(id); + return ResponseEntity.noContent() + .build(); } } diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 151a63945..0e84c0801 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -10,22 +10,23 @@ import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.repository.CategoryRepository; import codezap.global.exception.CodeZapException; +import codezap.template.repository.TemplateRepository; @Service public class CategoryService { private final CategoryRepository categoryRepository; + private final TemplateRepository templateRepository; - public CategoryService(CategoryRepository categoryRepository) { + public CategoryService(CategoryRepository categoryRepository, TemplateRepository templateRepository) { this.categoryRepository = categoryRepository; + this.templateRepository = templateRepository; } @Transactional public Long create(CreateCategoryRequest createCategoryRequest) { String categoryName = createCategoryRequest.name(); - if (categoryRepository.existsByName(categoryName)) { - throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재하고 있습니다."); - } + validateDuplicatedCategory(categoryName); Category category = new Category(categoryName); return categoryRepository.save(category).getId(); } @@ -36,11 +37,21 @@ public FindAllCategoriesResponse findAll() { @Transactional public void update(Long id, UpdateCategoryRequest updateCategoryRequest) { - if (categoryRepository.existsByName(updateCategoryRequest.name())) { - throw new CodeZapException(HttpStatus.CONFLICT, - "이름이 " + updateCategoryRequest.name() + "인 카테고리가 이미 존재하고 있습니다."); - } + validateDuplicatedCategory(updateCategoryRequest.name()); Category category = categoryRepository.fetchById(id); category.updateName(updateCategoryRequest.name()); } + + public void deleteById(Long id) { + if (templateRepository.existsByCategoryId(id)) { + throw new CodeZapException(HttpStatus.BAD_REQUEST, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다."); + } + categoryRepository.deleteById(id); + } + + private void validateDuplicatedCategory(String categoryName) { + if (categoryRepository.existsByName(categoryName)) { + throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재하고 있습니다."); + } + } } diff --git a/backend/src/main/java/codezap/template/repository/TemplateRepository.java b/backend/src/main/java/codezap/template/repository/TemplateRepository.java index 47b00b246..127eef4cf 100644 --- a/backend/src/main/java/codezap/template/repository/TemplateRepository.java +++ b/backend/src/main/java/codezap/template/repository/TemplateRepository.java @@ -12,4 +12,6 @@ default Template fetchById(Long id) { return findById(id).orElseThrow( () -> new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + id + "에 해당하는 템플릿이 존재하지 않습니다.")); } + + boolean existsByCategoryId(Long categoryId); } diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index b80149a6e..ec3d83feb 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -2,6 +2,8 @@ import static org.hamcrest.Matchers.is; +import java.util.List; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -16,6 +18,9 @@ import codezap.category.dto.request.CreateCategoryRequest; import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.service.CategoryService; +import codezap.template.dto.request.CreateSnippetRequest; +import codezap.template.dto.request.CreateTemplateRequest; +import codezap.template.service.TemplateService; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -31,6 +36,8 @@ class CategoryControllerTest { @Autowired private CategoryService categoryService; + @Autowired + private TemplateService templateService; @BeforeEach void setting() { @@ -142,4 +149,54 @@ void updateCategoryFailWithDuplicatedName() { .body("detail", is("이름이 " + duplicatedName + "인 카테고리가 이미 존재하고 있습니다.")); } } + + + @Nested + @DisplayName("카테고리 삭제 테스트") + class deleteCategoryTest { + + Long savedCategoryId; + + @BeforeEach + void saveCategory() { + savedCategoryId = categoryService.create(new CreateCategoryRequest("category1")); + } + + @Test + @DisplayName("카테고리 삭제 성공") + void deleteCategorySuccess() { + RestAssured.given().log().all() + .when().delete("/categories/" + savedCategoryId) + .then().log().all() + .statusCode(204); + } + + @Test + @DisplayName("카테고리 수정 성공: 존재하지 않는 카테고리의 삭제 요청") + void updateCategoryFailWithDuplicatedName() { + RestAssured.given().log().all() + .when().delete("/categories/" + savedCategoryId + 1) + .then().log().all() + .statusCode(204); + } + + @Test + @DisplayName("카테고리 삭제 실패: 템플릿이 존재하는 카테고리는 삭제 불가능") + void updateCategoryFailWithLongName() { + //given + templateService.create(new CreateTemplateRequest( + "title", + List.of(new CreateSnippetRequest("filename", "content", 1)), + savedCategoryId, + List.of("tag1", "tag2") + )); + + //when & then + RestAssured.given().log().all() + .when().delete("/categories/" + savedCategoryId) + .then().log().all() + .statusCode(400) + .body("detail", is("템플릿이 존재하는 카테고리는 삭제할 수 없습니다.")); + } + } } diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index 6dab5569f..d80d23413 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -89,4 +89,17 @@ void updateCategorySuccess() { //then assertThat(categoryRepository.fetchById(savedCategory.getId()).getName()).isEqualTo("updateName"); } + + @Test + @DisplayName("카테고리 삭제 성공") + void deleteCategorySuccess() { + //given + Category savedCategory = categoryRepository.save(new Category("category1")); + + //when + categoryService.deleteById(savedCategory.getId()); + + //then + assertThat(categoryRepository.findById(savedCategory.getId())).isEmpty(); + } } From fd8faf6cb10e6d58991578fd3db7338348688386 Mon Sep 17 00:00:00 2001 From: zangsu Date: Thu, 1 Aug 2024 21:01:55 +0900 Subject: [PATCH 020/168] =?UTF-8?q?docs(category):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 존재하고 있습니다 -> 존재합니다 로 변경 - 응답에 중복된 이름에 대한 예외 추가 --- .../controller/SpringDocCategoryController.java | 10 +++++++++- .../java/codezap/category/service/CategoryService.java | 2 +- .../category/controller/CategoryControllerTest.java | 2 +- .../codezap/category/service/CategoryServiceTest.java | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java b/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java index 621d23002..f3ad05bdf 100644 --- a/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java +++ b/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java @@ -26,7 +26,9 @@ public interface SpringDocCategoryController { @Header(name = "생성된 카테고리의 API 경로", example = "/categories/1")}) @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories", errorCases = { @ErrorCase(description = "모든 필드 중 null인 값이 있는 경우", exampleMessage = "카테고리 이름이 null 입니다."), - @ErrorCase(description = "카테고리 이름이 255자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 255자까지 입력 가능합니다.") + @ErrorCase(description = "카테고리 이름이 255자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 255자까지 입력 가능합니다."), + @ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", + exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다.") }) ResponseEntity createCategory(CreateCategoryRequest createCategoryRequest); @@ -40,10 +42,16 @@ public interface SpringDocCategoryController { @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories/1", errorCases = { @ErrorCase(description = "해당하는 id 값인 카테고리가 없는 경우", exampleMessage = "식별자 1에 해당하는 카테고리가 존재하지 않습니다."), + @ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", + exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다.") }) ResponseEntity updateCategory(Long id, UpdateCategoryRequest updateCategoryRequest); @Operation(summary = "카테고리 삭제", description = "해당하는 식별자의 카테고리를 삭제합니다.") @ApiResponse(responseCode = "204", description = "카테고리 삭제 성공") + @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories/1", errorCases = { + @ErrorCase(description = "삭제하려는 카테고리에 템플릿이 존재하는 경우", + exampleMessage = "템플릿이 존재하는 카테고리는 삭제할 수 없습니다."), + }) ResponseEntity deleteCategory(Long id); } diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 0e84c0801..48a9f5191 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -51,7 +51,7 @@ public void deleteById(Long id) { private void validateDuplicatedCategory(String categoryName) { if (categoryRepository.existsByName(categoryName)) { - throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재하고 있습니다."); + throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재합니다."); } } } diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index ec3d83feb..71b21afd3 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -146,7 +146,7 @@ void updateCategoryFailWithDuplicatedName() { .when().put("/categories/" + savedCategoryId) .then().log().all() .statusCode(409) - .body("detail", is("이름이 " + duplicatedName + "인 카테고리가 이미 존재하고 있습니다.")); + .body("detail", is("이름이 " + duplicatedName + "인 카테고리가 이미 존재합니다.")); } } diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index d80d23413..11a811216 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -62,7 +62,7 @@ void createCategoryFailWithDuplicateName() { assertThatThrownBy(() -> categoryService.create(createCategoryRequest)) .isInstanceOf(CodeZapException.class) - .hasMessage("이름이 " + createCategoryRequest.name() + "인 카테고리가 이미 존재하고 있습니다."); + .hasMessage("이름이 " + createCategoryRequest.name() + "인 카테고리가 이미 존재합니다."); } } From 7122fd46b5ac1135728b211ecd7b2676429b7d84 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 16:32:23 +0900 Subject: [PATCH 021/168] =?UTF-8?q?feat(domain):=20Category=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu --- .../codezap/category/domain/Category.java | 20 +++++++++++++++++++ .../codezap/template/domain/Template.java | 5 +++++ 2 files changed, 25 insertions(+) create mode 100644 backend/src/main/java/codezap/category/domain/Category.java diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java new file mode 100644 index 000000000..5654900b6 --- /dev/null +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -0,0 +1,20 @@ +package codezap.category.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import codezap.global.auditing.BaseTimeEntity; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +public class Category extends BaseTimeEntity { + @Id + private Long id; + + @Column(nullable = false) + private String name; +} diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index 1178d79c1..a00b75b63 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -5,7 +5,9 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import codezap.category.domain.Category; import codezap.global.auditing.BaseTimeEntity; import lombok.Getter; import lombok.NoArgsConstructor; @@ -22,6 +24,9 @@ public class Template extends BaseTimeEntity { @Column(nullable = false) private String title; + @ManyToOne(optional = false) + private Category category; + public Template(String title) { this.title = title; } From d13b2567dd757d391679d7d3f9b1d5105147bb43 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 16:41:16 +0900 Subject: [PATCH 022/168] =?UTF-8?q?feat(domain):=20Tag=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu --- .../java/codezap/template/domain/Tag.java | 21 +++++++++ .../codezap/template/domain/TemplateTag.java | 43 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 backend/src/main/java/codezap/template/domain/Tag.java create mode 100644 backend/src/main/java/codezap/template/domain/TemplateTag.java diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java new file mode 100644 index 000000000..73106efe6 --- /dev/null +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -0,0 +1,21 @@ +package codezap.template.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import codezap.global.auditing.BaseTimeEntity; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +public class Tag extends BaseTimeEntity { + + @Id + private Long id; + + @Column(nullable = false) + private String value; +} diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java new file mode 100644 index 000000000..7fa96742c --- /dev/null +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -0,0 +1,43 @@ +package codezap.template.domain; + +import java.io.Serializable; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MapsId; + +import codezap.global.auditing.BaseTimeEntity; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Entity +@RequiredArgsConstructor +@Getter +public class TemplateTag extends BaseTimeEntity { + + @Embeddable + @RequiredArgsConstructor + @Getter + @EqualsAndHashCode + private static class TemplateTagId implements Serializable { + private Long templateId; + private Long tagId; + } + + @EmbeddedId + private TemplateTagId id; + + @ManyToOne + @MapsId("templateId") + @JoinColumn(name = "template_id") + private Template template; + + @ManyToOne + @MapsId("tagId") + @JoinColumn(name = "tag_id") + private Tag tag; +} \ No newline at end of file From dd7fbb70840c44d0dae5afff6ea086bb8064f452 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 20:46:04 +0900 Subject: [PATCH 023/168] =?UTF-8?q?refactor(domain):=20Tag=EC=99=80=20Cate?= =?UTF-8?q?gory=EC=97=90=20=EA=B8=B0=EB=B3=B8=20=ED=82=A4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu --- backend/src/main/java/codezap/category/domain/Category.java | 3 +++ backend/src/main/java/codezap/template/domain/Tag.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index 5654900b6..7d96246ed 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -2,6 +2,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import codezap.global.auditing.BaseTimeEntity; @@ -13,6 +15,7 @@ @NoArgsConstructor public class Category extends BaseTimeEntity { @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java index 73106efe6..7859a3f61 100644 --- a/backend/src/main/java/codezap/template/domain/Tag.java +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -2,6 +2,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import codezap.global.auditing.BaseTimeEntity; @@ -14,6 +16,7 @@ public class Tag extends BaseTimeEntity { @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) From 7ebdb48f4138d7d8670836443d2d9aad35389146 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 20:50:51 +0900 Subject: [PATCH 024/168] =?UTF-8?q?style(service):=20test=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B0=9C=ED=96=89=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: zangsu --- .../java/codezap/template/service/TemplateServiceTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 0bde5fd7a..fea66f4c4 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -34,15 +34,18 @@ @Sql(value = "/clear.sql", executionPhase = ExecutionPhase.AFTER_TEST_CLASS) class TemplateServiceTest { + @LocalServerPort + int port; + @Autowired private TemplateService templateService; - @LocalServerPort - int port; @Autowired private TemplateRepository templateRepository; + @Autowired private SnippetRepository snippetRepository; + @Autowired private ThumbnailSnippetRepository thumbnailSnippetRepository; From 4ee79287ea54f4d287eb437b3ceafd36837b5e95 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 20:54:28 +0900 Subject: [PATCH 025/168] =?UTF-8?q?feat(category):=20category=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 29 +++++++++++++ .../codezap/category/domain/Category.java | 5 +++ .../dto/request/CreateCategoryRequest.java | 6 +++ .../repository/CategoryRepository.java | 8 ++++ .../category/service/CategoryService.java | 24 +++++++++++ .../controller/CategoryControllerTest.java | 42 ++++++++++++++++++ .../category/service/CategoryServiceTest.java | 43 +++++++++++++++++++ backend/src/test/resources/clear.sql | 34 +++++++++++++++ 8 files changed, 191 insertions(+) create mode 100644 backend/src/main/java/codezap/category/controller/CategoryController.java create mode 100644 backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java create mode 100644 backend/src/main/java/codezap/category/repository/CategoryRepository.java create mode 100644 backend/src/main/java/codezap/category/service/CategoryService.java create mode 100644 backend/src/test/java/codezap/category/controller/CategoryControllerTest.java create mode 100644 backend/src/test/java/codezap/category/service/CategoryServiceTest.java diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java new file mode 100644 index 000000000..982706b6e --- /dev/null +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -0,0 +1,29 @@ +package codezap.category.controller; + +import java.net.URI; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.service.CategoryService; + +@RestController +@RequestMapping("/categories") +public class CategoryController { + + private final CategoryService categoryService; + + public CategoryController(CategoryService categoryService) { + this.categoryService = categoryService; + } + + @PostMapping + public ResponseEntity createCategory(@RequestBody CreateCategoryRequest createCategoryRequest) { + return ResponseEntity.created(URI.create("/categories/" + categoryService.create(createCategoryRequest))) + .build(); + } +} diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index 7d96246ed..0c4aa47da 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -20,4 +20,9 @@ public class Category extends BaseTimeEntity { @Column(nullable = false) private String name; + + public Category(String name) { + this.id = null; + this.name = name; + } } diff --git a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java new file mode 100644 index 000000000..892495166 --- /dev/null +++ b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java @@ -0,0 +1,6 @@ +package codezap.category.dto.request; + +public record CreateCategoryRequest( + String name +) { +} diff --git a/backend/src/main/java/codezap/category/repository/CategoryRepository.java b/backend/src/main/java/codezap/category/repository/CategoryRepository.java new file mode 100644 index 000000000..8b3644a89 --- /dev/null +++ b/backend/src/main/java/codezap/category/repository/CategoryRepository.java @@ -0,0 +1,8 @@ +package codezap.category.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import codezap.category.domain.Category; + +public interface CategoryRepository extends JpaRepository { +} diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java new file mode 100644 index 000000000..7dcb46b59 --- /dev/null +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -0,0 +1,24 @@ +package codezap.category.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import codezap.category.domain.Category; +import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.repository.CategoryRepository; + +@Service +public class CategoryService { + + private final CategoryRepository categoryRepository; + + public CategoryService(CategoryRepository categoryRepository) { + this.categoryRepository = categoryRepository; + } + + @Transactional + public Long create(CreateCategoryRequest createCategoryRequest) { + Category category = new Category(createCategoryRequest.name()); + return categoryRepository.save(category).getId(); + } +} diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java new file mode 100644 index 000000000..00d7cee6f --- /dev/null +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -0,0 +1,42 @@ +package codezap.category.controller; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.Sql.ExecutionPhase; + +import codezap.category.dto.request.CreateCategoryRequest; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@Sql(value = "/clear.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) +@Sql(value = "/clear.sql", executionPhase = ExecutionPhase.AFTER_TEST_CLASS) +class CategoryControllerTest { + + @LocalServerPort + int port; + + @BeforeEach + void setting() { + RestAssured.port = port; + } + + @Test + @DisplayName("카테고리 생성 성공") + void createCategorySuccess() { + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category"); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(createCategoryRequest) + .when().post("/categories") + .then().log().all() + .header("Location", "/categories/1") + .statusCode(201); + } +} \ No newline at end of file diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java new file mode 100644 index 000000000..1c98c92b7 --- /dev/null +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -0,0 +1,43 @@ +package codezap.category.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.Sql.ExecutionPhase; + +import codezap.category.dto.request.CreateCategoryRequest; +import io.restassured.RestAssured; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@Sql(value = "/clear.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) +@Sql(value = "/clear.sql", executionPhase = ExecutionPhase.AFTER_TEST_CLASS) +class CategoryServiceTest { + + @LocalServerPort + int port; + + @Autowired + private CategoryService categoryService; + + @BeforeEach + void setting() { + RestAssured.port = port; + } + + @Test + @DisplayName("카테고리 생성 성공") + void createCategorySuccess() { + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category1"); + + Long categoryId = categoryService.create(createCategoryRequest); + + assertThat(categoryId).isEqualTo(1L); + } +} \ No newline at end of file diff --git a/backend/src/test/resources/clear.sql b/backend/src/test/resources/clear.sql index 845a987b2..8cb83de09 100644 --- a/backend/src/test/resources/clear.sql +++ b/backend/src/test/resources/clear.sql @@ -1,16 +1,50 @@ DROP TABLE IF EXISTS thumbnail_snippet; DROP TABLE IF EXISTS snippet; +DROP TABLE IF EXISTS template_tag; +DROP TABLE IF EXISTS tag; DROP TABLE IF EXISTS template; +DROP TABLE IF EXISTS category; + +CREATE TABLE category +( + id BIGINT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + created_at DATETIME(6) NOT NULL, + modified_at DATETIME(6) NOT NULL, + PRIMARY KEY (id) +); CREATE TABLE template ( id BIGINT NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL, + category_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + modified_at DATETIME(6) NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (category_id) REFERENCES category (id) +); + +create table tag +( + id BIGINT NOT NULL auto_increment, + value VARCHAR(255) NOT NULL, created_at DATETIME(6) NOT NULL, modified_at DATETIME(6) NOT NULL, PRIMARY KEY (id) ); +create table template_tag +( + template_id BIGINT NOT NULL, + tag_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + modified_at DATETIME(6) NOT NULL, + PRIMARY KEY (template_id, tag_id), + FOREIGN KEY (template_id) REFERENCES template (id), + FOREIGN KEY (tag_id) REFERENCES tag (id) +); + CREATE TABLE snippet ( id BIGINT NOT NULL AUTO_INCREMENT, From 786a089ad6dafd644b51a0440c6cb552e6eb2067 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 21:03:23 +0900 Subject: [PATCH 026/168] =?UTF-8?q?feat(category):=20category=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 4 +- .../dto/request/CreateCategoryRequest.java | 5 ++ .../controller/CategoryControllerTest.java | 47 ++++++++++++++----- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 982706b6e..6f546ef1e 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -2,6 +2,8 @@ import java.net.URI; +import jakarta.validation.Valid; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -22,7 +24,7 @@ public CategoryController(CategoryService categoryService) { } @PostMapping - public ResponseEntity createCategory(@RequestBody CreateCategoryRequest createCategoryRequest) { + public ResponseEntity createCategory(@Valid @RequestBody CreateCategoryRequest createCategoryRequest) { return ResponseEntity.created(URI.create("/categories/" + categoryService.create(createCategoryRequest))) .build(); } diff --git a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java index 892495166..7d1276e94 100644 --- a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java +++ b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java @@ -1,6 +1,11 @@ package codezap.category.dto.request; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + public record CreateCategoryRequest( + @NotNull(message = "카테고리 이름이 null 입니다.") + @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.") String name ) { } diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index 00d7cee6f..edfbf41a2 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -1,7 +1,10 @@ package codezap.category.controller; +import static org.hamcrest.Matchers.is; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @@ -18,6 +21,8 @@ @Sql(value = "/clear.sql", executionPhase = ExecutionPhase.AFTER_TEST_CLASS) class CategoryControllerTest { + private static final int MAX_LENGTH = 255; + @LocalServerPort int port; @@ -26,17 +31,35 @@ void setting() { RestAssured.port = port; } - @Test - @DisplayName("카테고리 생성 성공") - void createCategorySuccess() { - CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category"); - - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .body(createCategoryRequest) - .when().post("/categories") - .then().log().all() - .header("Location", "/categories/1") - .statusCode(201); + @Nested + @DisplayName("카테고리 생성 테스트") + class createCategoryTest { + @Test + @DisplayName("카테고리 생성 성공") + void createCategorySuccess() { + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("a".repeat(MAX_LENGTH)); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(createCategoryRequest) + .when().post("/categories") + .then().log().all() + .header("Location", "/categories/1") + .statusCode(201); + } + + @Test + @DisplayName("카테고리 생성 실패:") + void createCategoryFail() { + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("a".repeat(MAX_LENGTH + 1)); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(createCategoryRequest) + .when().post("/categories") + .then().log().all() + .statusCode(400) + .body("detail", is("카테고리 이름은 최대 255자까지 입력 가능합니다.")); + } } } \ No newline at end of file From 778f92a2cc2f4e67bd359c836e29ba959017aaa3 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 21:29:18 +0900 Subject: [PATCH 027/168] =?UTF-8?q?feat(category):=20category=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=A6=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/category/domain/Category.java | 2 +- .../repository/CategoryRepository.java | 1 + .../category/service/CategoryService.java | 8 ++++- .../category/service/CategoryServiceTest.java | 35 +++++++++++++++---- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index 0c4aa47da..59c7ef843 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -18,7 +18,7 @@ public class Category extends BaseTimeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false) + @Column(nullable = false, unique = true) private String name; public Category(String name) { diff --git a/backend/src/main/java/codezap/category/repository/CategoryRepository.java b/backend/src/main/java/codezap/category/repository/CategoryRepository.java index 8b3644a89..fe335048a 100644 --- a/backend/src/main/java/codezap/category/repository/CategoryRepository.java +++ b/backend/src/main/java/codezap/category/repository/CategoryRepository.java @@ -5,4 +5,5 @@ import codezap.category.domain.Category; public interface CategoryRepository extends JpaRepository { + boolean existsByName(String name); } diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 7dcb46b59..b1e52b55e 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -1,11 +1,13 @@ package codezap.category.service; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; import codezap.category.repository.CategoryRepository; +import codezap.global.exception.CodeZapException; @Service public class CategoryService { @@ -18,7 +20,11 @@ public CategoryService(CategoryRepository categoryRepository) { @Transactional public Long create(CreateCategoryRequest createCategoryRequest) { - Category category = new Category(createCategoryRequest.name()); + String categoryName = createCategoryRequest.name(); + if(categoryRepository.existsByName(categoryName)) { + throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재하고 있습니다."); + } + Category category = new Category(categoryName); return categoryRepository.save(category).getId(); } } diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index 1c98c92b7..167ab9b12 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -1,9 +1,11 @@ package codezap.category.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -12,7 +14,10 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.repository.CategoryRepository; +import codezap.global.exception.CodeZapException; import io.restassured.RestAssured; @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @@ -26,18 +31,36 @@ class CategoryServiceTest { @Autowired private CategoryService categoryService; + @Autowired + private CategoryRepository categoryRepository; + @BeforeEach void setting() { RestAssured.port = port; } - @Test - @DisplayName("카테고리 생성 성공") - void createCategorySuccess() { - CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category1"); + @Nested + @DisplayName("카테고리 생성 테스트") + class createCategoryTest { + @Test + @DisplayName("카테고리 생성 성공") + void createCategorySuccess() { + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category1"); + + Long categoryId = categoryService.create(createCategoryRequest); + + assertThat(categoryId).isEqualTo(1L); + } - Long categoryId = categoryService.create(createCategoryRequest); + @Test + @DisplayName("카테고리 생성 실패: 중복된 이름의 카테고리 이름 생성") + void createCategoryFailWithDuplicateName() { + categoryRepository.save(new Category("category")); + CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("category"); - assertThat(categoryId).isEqualTo(1L); + assertThatThrownBy(() -> categoryService.create(createCategoryRequest)) + .isInstanceOf(CodeZapException.class) + .hasMessage("이름이 " + createCategoryRequest.name() + "인 카테고리가 이미 존재하고 있습니다."); + } } } \ No newline at end of file From d64dfa272945302d8d2bc9ea05e8e9084e2b93ee Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 21:49:42 +0900 Subject: [PATCH 028/168] =?UTF-8?q?feat(category):=20category=20=EC=A0=84?= =?UTF-8?q?=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 7 +++++++ .../response/FindAllCategoriesResponse.java | 17 +++++++++++++++ .../response/FindCategoryByIdResponse.java | 12 +++++++++++ .../category/service/CategoryService.java | 5 +++++ .../controller/CategoryControllerTest.java | 21 +++++++++++++++++++ .../category/service/CategoryServiceTest.java | 12 +++++++++++ 6 files changed, 74 insertions(+) create mode 100644 backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java create mode 100644 backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 6f546ef1e..27e50a673 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -5,12 +5,14 @@ import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.service.CategoryService; @RestController @@ -28,4 +30,9 @@ public ResponseEntity createCategory(@Valid @RequestBody CreateCategoryReq return ResponseEntity.created(URI.create("/categories/" + categoryService.create(createCategoryRequest))) .build(); } + + @GetMapping + public ResponseEntity getCategories() { + return ResponseEntity.ok(categoryService.findAll()); + } } diff --git a/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java b/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java new file mode 100644 index 000000000..e7df7c5a6 --- /dev/null +++ b/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java @@ -0,0 +1,17 @@ +package codezap.category.dto.response; + +import java.util.List; + +import codezap.category.domain.Category; + +public record FindAllCategoriesResponse( + List categories +) { + public static FindAllCategoriesResponse from(List categories) { + return new FindAllCategoriesResponse( + categories.stream() + .map(FindCategoryByIdResponse::from) + .toList() + ); + } +} diff --git a/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java b/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java new file mode 100644 index 000000000..fe7909aec --- /dev/null +++ b/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java @@ -0,0 +1,12 @@ +package codezap.category.dto.response; + +import codezap.category.domain.Category; + +public record FindCategoryByIdResponse( + Long id, + String name +) { + public static FindCategoryByIdResponse from(Category category) { + return new FindCategoryByIdResponse(category.getId(), category.getName()); + } +} diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index b1e52b55e..02a83af88 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -6,6 +6,7 @@ import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.repository.CategoryRepository; import codezap.global.exception.CodeZapException; @@ -27,4 +28,8 @@ public Long create(CreateCategoryRequest createCategoryRequest) { Category category = new Category(categoryName); return categoryRepository.save(category).getId(); } + + public FindAllCategoriesResponse findAll() { + return FindAllCategoriesResponse.from(categoryRepository.findAll()); + } } diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index edfbf41a2..16f519f83 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.server.LocalServerPort; @@ -13,6 +14,7 @@ import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.service.CategoryService; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -26,6 +28,9 @@ class CategoryControllerTest { @LocalServerPort int port; + @Autowired + private CategoryService categoryService; + @BeforeEach void setting() { RestAssured.port = port; @@ -62,4 +67,20 @@ void createCategoryFail() { .body("detail", is("카테고리 이름은 최대 255자까지 입력 가능합니다.")); } } + + @Test + @DisplayName("카테고리 전체 조회 성공") + void findAllCategoriesSuccess() { + CreateCategoryRequest createCategoryRequest1 = new CreateCategoryRequest("category1"); + CreateCategoryRequest createCategoryRequest2 = new CreateCategoryRequest("category2"); + categoryService.create(createCategoryRequest1); + categoryService.create(createCategoryRequest2); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .when().get("/categories") + .then().log().all() + .statusCode(200) + .body("categories.size()", is(2)); + } } \ No newline at end of file diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index 167ab9b12..944a184af 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -16,6 +16,7 @@ import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.repository.CategoryRepository; import codezap.global.exception.CodeZapException; import io.restassured.RestAssured; @@ -63,4 +64,15 @@ void createCategoryFailWithDuplicateName() { .hasMessage("이름이 " + createCategoryRequest.name() + "인 카테고리가 이미 존재하고 있습니다."); } } + + @Test + @DisplayName("카테고리 전체 조회 테스트") + void findAllCategoriesSuccess() { + categoryRepository.save(new Category("category1")); + categoryRepository.save(new Category("category2")); + + FindAllCategoriesResponse findAllCategoriesResponse = categoryService.findAll(); + + assertThat(findAllCategoriesResponse.categories()).hasSize(2); + } } \ No newline at end of file From 05fe337a6fff440339f1f7b1fa6435a3473fe48e Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 30 Jul 2024 21:51:45 +0900 Subject: [PATCH 029/168] =?UTF-8?q?refactor(domain):=20lombok=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B5=AC=ED=98=84=20=EC=88=9C=EC=84=9C?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/codezap/category/domain/Category.java | 2 +- backend/src/main/java/codezap/template/domain/Snippet.java | 2 +- backend/src/main/java/codezap/template/domain/Tag.java | 2 +- backend/src/main/java/codezap/template/domain/Template.java | 2 +- backend/src/main/java/codezap/template/domain/TemplateTag.java | 3 ++- .../main/java/codezap/template/domain/ThumbnailSnippet.java | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index 59c7ef843..f28666022 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -11,8 +11,8 @@ import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor +@Getter public class Category extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/backend/src/main/java/codezap/template/domain/Snippet.java b/backend/src/main/java/codezap/template/domain/Snippet.java index 8cf0291a5..c85b0ee36 100644 --- a/backend/src/main/java/codezap/template/domain/Snippet.java +++ b/backend/src/main/java/codezap/template/domain/Snippet.java @@ -16,8 +16,8 @@ import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor +@Getter public class Snippet extends BaseTimeEntity { private static final String CODE_LINE_BREAK = "\n"; diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java index 7859a3f61..0c2208952 100644 --- a/backend/src/main/java/codezap/template/domain/Tag.java +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -11,8 +11,8 @@ import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor +@Getter public class Tag extends BaseTimeEntity { @Id diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index a00b75b63..29e518068 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -13,8 +13,8 @@ import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor +@Getter public class Template extends BaseTimeEntity { @Id diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java index 7fa96742c..72729dbb4 100644 --- a/backend/src/main/java/codezap/template/domain/TemplateTag.java +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -12,10 +12,11 @@ import codezap.global.auditing.BaseTimeEntity; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; @Entity -@RequiredArgsConstructor +@NoArgsConstructor @Getter public class TemplateTag extends BaseTimeEntity { diff --git a/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java b/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java index 8c6b5feea..920dc18d2 100644 --- a/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java +++ b/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java @@ -11,8 +11,8 @@ import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor +@Getter public class ThumbnailSnippet extends BaseTimeEntity { @Id From 2fc9651a1ec6fb8de4fd62d70f25eb1704625eb7 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Wed, 31 Jul 2024 13:26:34 +0900 Subject: [PATCH 030/168] =?UTF-8?q?refactor(domain):=20Tag=20value=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=A5=BC=20name=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/codezap/template/domain/Tag.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java index 0c2208952..8098b5d29 100644 --- a/backend/src/main/java/codezap/template/domain/Tag.java +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -20,5 +20,5 @@ public class Tag extends BaseTimeEntity { private Long id; @Column(nullable = false) - private String value; + private String name; } From d12f79781e8d6969ca581346997e03816cf2876c Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Wed, 31 Jul 2024 15:32:14 +0900 Subject: [PATCH 031/168] =?UTF-8?q?feat(codezap):=20template=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B8=B0=EB=8A=A5=EC=97=90=20tag=EC=99=80=20catego?= =?UTF-8?q?ry=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/category/domain/Category.java | 1 - .../repository/CategoryRepository.java | 8 ++++++ .../java/codezap/template/domain/Tag.java | 4 +++ .../codezap/template/domain/Template.java | 6 +++- .../codezap/template/domain/TemplateTag.java | 11 +++++++- .../dto/request/CreateTemplateRequest.java | 6 +++- .../template/repository/TagRepository.java | 8 ++++++ .../repository/TemplateTagRepository.java | 8 ++++++ .../template/service/TemplateService.java | 28 +++++++++++++++++-- 9 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 backend/src/main/java/codezap/template/repository/TagRepository.java create mode 100644 backend/src/main/java/codezap/template/repository/TemplateTagRepository.java diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index f28666022..5ffb71ed2 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -22,7 +22,6 @@ public class Category extends BaseTimeEntity { private String name; public Category(String name) { - this.id = null; this.name = name; } } diff --git a/backend/src/main/java/codezap/category/repository/CategoryRepository.java b/backend/src/main/java/codezap/category/repository/CategoryRepository.java index fe335048a..0f710a2c4 100644 --- a/backend/src/main/java/codezap/category/repository/CategoryRepository.java +++ b/backend/src/main/java/codezap/category/repository/CategoryRepository.java @@ -1,9 +1,17 @@ package codezap.category.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.http.HttpStatus; import codezap.category.domain.Category; +import codezap.global.exception.CodeZapException; public interface CategoryRepository extends JpaRepository { + + default Category fetchById(Long id) { + return findById(id).orElseThrow( + () -> new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + id + "에 해당하는 카테고리가 존재하지 않습니다.")); + } + boolean existsByName(String name); } diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java index 8098b5d29..eb7bf6fd7 100644 --- a/backend/src/main/java/codezap/template/domain/Tag.java +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -21,4 +21,8 @@ public class Tag extends BaseTimeEntity { @Column(nullable = false) private String name; + + public Tag(String name) { + this.name = name; + } } diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index 29e518068..bd55d4103 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -24,11 +24,15 @@ public class Template extends BaseTimeEntity { @Column(nullable = false) private String title; + @Column(columnDefinition = "TEXT") + private String description; + @ManyToOne(optional = false) private Category category; - public Template(String title) { + public Template(String title, Category category) { this.title = title; + this.category = category; } public void updateTitle(String title) { diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java index 72729dbb4..0d46698ee 100644 --- a/backend/src/main/java/codezap/template/domain/TemplateTag.java +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -10,10 +10,12 @@ import jakarta.persistence.MapsId; import codezap.global.auditing.BaseTimeEntity; +import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; +import lombok.Setter; @Entity @NoArgsConstructor @@ -21,7 +23,8 @@ public class TemplateTag extends BaseTimeEntity { @Embeddable - @RequiredArgsConstructor + @NoArgsConstructor + @AllArgsConstructor @Getter @EqualsAndHashCode private static class TemplateTagId implements Serializable { @@ -41,4 +44,10 @@ private static class TemplateTagId implements Serializable { @MapsId("tagId") @JoinColumn(name = "tag_id") private Tag tag; + + public TemplateTag(Template template, Tag tag) { + this.id = new TemplateTagId(template.getId(), tag.getId()); + this.template = template; + this.tag = tag; + } } \ No newline at end of file diff --git a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java index 9663ba500..9527ff657 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java @@ -18,7 +18,11 @@ public record CreateTemplateRequest( @Schema(description = "템플릿의 스니펫 내역") @NotNull(message = "스니펫 리스트가 null 입니다.") @Valid - List snippets + List snippets, + + Long categoryId, + + List tags ) implements ValidatedSnippetsOrdinalRequest { @Override public List extractSnippetsOrdinal() { diff --git a/backend/src/main/java/codezap/template/repository/TagRepository.java b/backend/src/main/java/codezap/template/repository/TagRepository.java new file mode 100644 index 000000000..e813662e8 --- /dev/null +++ b/backend/src/main/java/codezap/template/repository/TagRepository.java @@ -0,0 +1,8 @@ +package codezap.template.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import codezap.template.domain.Tag; + +public interface TagRepository extends JpaRepository { +} diff --git a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java new file mode 100644 index 000000000..5396f030c --- /dev/null +++ b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java @@ -0,0 +1,8 @@ +package codezap.template.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import codezap.template.domain.TemplateTag; + +public interface TemplateTagRepository extends JpaRepository { +} diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 57be444c7..8adb4f6aa 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -6,8 +6,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import codezap.category.domain.Category; +import codezap.category.repository.CategoryRepository; import codezap.template.domain.Snippet; +import codezap.template.domain.Tag; import codezap.template.domain.Template; +import codezap.template.domain.TemplateTag; import codezap.template.domain.ThumbnailSnippet; import codezap.template.dto.request.CreateSnippetRequest; import codezap.template.dto.request.CreateTemplateRequest; @@ -16,7 +20,9 @@ import codezap.template.dto.response.FindAllTemplatesResponse; import codezap.template.dto.response.FindTemplateByIdResponse; import codezap.template.repository.SnippetRepository; +import codezap.template.repository.TagRepository; import codezap.template.repository.TemplateRepository; +import codezap.template.repository.TemplateTagRepository; import codezap.template.repository.ThumbnailSnippetRepository; @Service @@ -27,19 +33,35 @@ public class TemplateService { private final ThumbnailSnippetRepository thumbnailSnippetRepository; private final TemplateRepository templateRepository; private final SnippetRepository snippetRepository; + private final CategoryRepository categoryRepository; + private final TagRepository tagRepository; + private final TemplateTagRepository templateTagRepository; public TemplateService(ThumbnailSnippetRepository thumbnailSnippetRepository, - TemplateRepository templateRepository, SnippetRepository snippetRepository + TemplateRepository templateRepository, SnippetRepository snippetRepository, + CategoryRepository categoryRepository, TagRepository tagRepository, + TemplateTagRepository templateTagRepository ) { this.thumbnailSnippetRepository = thumbnailSnippetRepository; this.templateRepository = templateRepository; this.snippetRepository = snippetRepository; + this.categoryRepository = categoryRepository; + this.tagRepository = tagRepository; + this.templateTagRepository = templateTagRepository; } @Transactional public Long create(CreateTemplateRequest createTemplateRequest) { - Template template = templateRepository.save( - new Template(createTemplateRequest.title())); + Category category = categoryRepository.fetchById(createTemplateRequest.categoryId()); + + Template template = templateRepository.save(new Template(createTemplateRequest.title(), category)); + + List tags = createTemplateRequest.tags().stream() + .map(Tag::new) + .map(tagRepository::save) + .toList(); + + tags.forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); createTemplateRequest.snippets() .forEach(createSnippetRequest -> createSnippet(createSnippetRequest, template)); From 8df56e453543a3582c63ef0dc768482b6ee294cd Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Wed, 31 Jul 2024 16:04:00 +0900 Subject: [PATCH 032/168] =?UTF-8?q?refactor(test):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TemplateControllerTest.java | 48 +++++++++++++++---- .../repository/SnippetRepositoryTest.java | 10 +++- .../template/service/TemplateServiceTest.java | 12 ++++- backend/src/test/resources/clear.sql | 3 +- 4 files changed, 60 insertions(+), 13 deletions(-) diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 12833e4ef..b8659010f 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -17,6 +17,8 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.service.CategoryService; import codezap.template.dto.request.CreateSnippetRequest; import codezap.template.dto.request.CreateTemplateRequest; import codezap.template.dto.request.UpdateSnippetRequest; @@ -35,6 +37,9 @@ class TemplateControllerTest { @Autowired private TemplateService templateService; + @Autowired + private CategoryService categoryService; + @LocalServerPort int port; @@ -52,9 +57,13 @@ class createTemplateTest { @CsvSource({"a, 65535", "ㄱ, 21845"}) void createTemplateSuccess(String repeatTarget, int maxLength) { String maxTitle = "a".repeat(MAX_LENGTH); + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest(maxTitle, - List.of(new CreateSnippetRequest("a".repeat(MAX_LENGTH), repeatTarget.repeat(maxLength), 1))); - + List.of(new CreateSnippetRequest("a".repeat(MAX_LENGTH), repeatTarget.repeat(maxLength), 1)), + 1L, + List.of("tag1", "tag2") + ); + RestAssured.given().log().all() .contentType(ContentType.JSON) .body(templateRequest) @@ -68,9 +77,13 @@ void createTemplateSuccess(String repeatTarget, int maxLength) { @DisplayName("템플릿 생성 실패: 템플릿 이름 길이 초과") void createTemplateFailWithLongTitle() { String exceededTitle = "a".repeat(MAX_LENGTH + 1); + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest(exceededTitle, - List.of(new CreateSnippetRequest("a", "content", 1))); - + List.of(new CreateSnippetRequest("a", "content", 1)), + 1L, + List.of("tag1", "tag2") + ); + RestAssured.given().log().all() .contentType(ContentType.JSON) .body(templateRequest) @@ -84,8 +97,12 @@ void createTemplateFailWithLongTitle() { @DisplayName("템플릿 생성 실패: 파일 이름 길이 초과") void createTemplateFailWithLongFileName() { String exceededTitle = "a".repeat(MAX_LENGTH + 1); + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest("title", - List.of(new CreateSnippetRequest(exceededTitle, "content", 1))); + List.of(new CreateSnippetRequest(exceededTitle, "content", 1)), + 1L, + List.of("tag1", "tag2") + ); RestAssured.given().log().all() .contentType(ContentType.JSON) @@ -100,8 +117,12 @@ void createTemplateFailWithLongFileName() { @DisplayName("템플릿 생성 실패: 파일 내용 길이 초과") @CsvSource({"a, 65536", "ㄱ, 21846"}) void createTemplateFailWithLongContent(String repeatTarget, int exceededLength) { + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest("title", - List.of(new CreateSnippetRequest("title", repeatTarget.repeat(exceededLength), 1))); + List.of(new CreateSnippetRequest("title", repeatTarget.repeat(exceededLength), 1)), + 1L, + List.of("tag1", "tag2") + ); RestAssured.given().log().all() .contentType(ContentType.JSON) @@ -116,9 +137,13 @@ void createTemplateFailWithLongContent(String repeatTarget, int exceededLength) @DisplayName("템플릿 생성 실패: 잘못된 스니펫 순서 입력") @CsvSource({"0, 1", "1, 3", "2, 1"}) void createTemplateFailWithWrongSnippetOrdinal(int firstIndex, int secondIndex) { + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest("title", List.of(new CreateSnippetRequest("title", "content", firstIndex), - new CreateSnippetRequest("title", "content", secondIndex))); + new CreateSnippetRequest("title", "content", secondIndex)), + 1L, + List.of("tag1", "tag2") + ); RestAssured.given().log().all() .contentType(ContentType.JSON) @@ -136,6 +161,7 @@ void findAllTemplatesSuccess() { // given CreateTemplateRequest templateRequest1 = createTemplateRequestWithTwoSnippets("title1"); CreateTemplateRequest templateRequest2 = createTemplateRequestWithTwoSnippets("title2"); + categoryService.create(new CreateCategoryRequest("category")); templateService.create(templateRequest1); templateService.create(templateRequest2); @@ -155,6 +181,7 @@ class findTemplateTest { @DisplayName("템플릿 상세 조회 성공") void findOneTemplateSuccess() { // given + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -187,6 +214,7 @@ class updateTemplateTest { @DisplayName("템플릿 수정 성공") void updateTemplateSuccess() { // given + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -217,6 +245,7 @@ void updateTemplateSuccess() { @CsvSource({"1, 2, 1", "3, 2, 1", "0, 2, 1"}) void updateTemplateFailWithWrongSnippetOrdinal(int createOrdinal1, int createOrdinal2, int updateOrdinal) { // given + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -251,6 +280,7 @@ class deleteTemplateTest { @DisplayName("템플릿 삭제 성공") void deleteTemplateSuccess() { // given + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -279,7 +309,9 @@ private static CreateTemplateRequest createTemplateRequestWithTwoSnippets(String List.of( new CreateSnippetRequest("filename1", "content1", 1), new CreateSnippetRequest("filename2", "content2", 2) - ) + ), + 1L, + List.of("tag1", "tag2") ); return templateRequest; } diff --git a/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java b/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java index 750ce4055..075ee12c7 100644 --- a/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java +++ b/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java @@ -13,6 +13,8 @@ import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.transaction.annotation.Transactional; +import codezap.category.domain.Category; +import codezap.category.repository.CategoryRepository; import codezap.template.domain.Snippet; import codezap.template.domain.Template; @@ -26,11 +28,14 @@ class SnippetRepositoryTest { private SnippetRepository snippetRepository; @Autowired private TemplateRepository templateRepository; + @Autowired + private CategoryRepository categoryRepository; @Test @DisplayName("단일 스니펫 찾기 성공: 템플릿과 순서") void findOneSnippetSuccessWithTemplateAndOrdinal() { - Template template = templateRepository.save(new Template("title")); + Category category = categoryRepository.save(new Category("category")); + Template template = templateRepository.save(new Template("title", category)); Snippet snippet1 = snippetRepository.save(new Snippet(template, "filename1", "content1", 1)); Snippet snippet2 = snippetRepository.save(new Snippet(template, "filename2", "content2", 2)); @@ -47,7 +52,8 @@ void findOneSnippetSuccessWithTemplateAndOrdinal() { @Test @DisplayName("스니펫 리스트 찾기 성공: 템플릿과 순서") void findSnippetsSuccessWithTemplateAndOrdinal() { - Template template = templateRepository.save(new Template("title")); + Category category = categoryRepository.save(new Category("category")); + Template template = templateRepository.save(new Template("title", category)); Snippet snippet1 = snippetRepository.save(new Snippet(template, "filename1", "content1", 1)); Snippet snippet2 = snippetRepository.save(new Snippet(template, "filename2", "content2", 2)); Snippet snippet3 = snippetRepository.save(new Snippet(template, "filename3", "content3", 2)); diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index fea66f4c4..27cb2591d 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -15,6 +15,8 @@ import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import codezap.category.domain.Category; +import codezap.category.repository.CategoryRepository; import codezap.template.domain.Snippet; import codezap.template.domain.Template; import codezap.template.domain.ThumbnailSnippet; @@ -48,6 +50,8 @@ class TemplateServiceTest { @Autowired private ThumbnailSnippetRepository thumbnailSnippetRepository; + @Autowired + private CategoryRepository categoryRepository; @BeforeEach void setting() { @@ -59,6 +63,7 @@ void setting() { void createTemplateSuccess() { // given CreateTemplateRequest createTemplateRequest = makeTemplateRequest("title"); + categoryRepository.save(new Category("category")); // when templateService.create(createTemplateRequest); @@ -143,7 +148,9 @@ private CreateTemplateRequest makeTemplateRequest(String title) { List.of( new CreateSnippetRequest("filename1", "content1", 1), new CreateSnippetRequest("filename2", "content2", 2) - ) + ), + 1L, + List.of("tag1", "tag2") ); } @@ -162,7 +169,8 @@ private UpdateTemplateRequest makeUpdateTemplateRequest(String title) { } private Template saveTemplate(CreateTemplateRequest createTemplateRequest) { - Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title())); + Category category = categoryRepository.save(new Category("category")); + Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title(), category)); Snippet savedFirstSnippet = snippetRepository.save(new Snippet(savedTemplate, "filename1", "content1", 1)); snippetRepository.save(new Snippet(savedTemplate, "filename2", "content2", 2)); thumbnailSnippetRepository.save(new ThumbnailSnippet(savedTemplate, savedFirstSnippet)); diff --git a/backend/src/test/resources/clear.sql b/backend/src/test/resources/clear.sql index 8cb83de09..9aada0497 100644 --- a/backend/src/test/resources/clear.sql +++ b/backend/src/test/resources/clear.sql @@ -18,6 +18,7 @@ CREATE TABLE template ( id BIGINT NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL, + description TEXT, category_id BIGINT NOT NULL, created_at DATETIME(6) NOT NULL, modified_at DATETIME(6) NOT NULL, @@ -28,7 +29,7 @@ CREATE TABLE template create table tag ( id BIGINT NOT NULL auto_increment, - value VARCHAR(255) NOT NULL, + name VARCHAR(255) NOT NULL, created_at DATETIME(6) NOT NULL, modified_at DATETIME(6) NOT NULL, PRIMARY KEY (id) From fae0611fcd5f4880a50c465c45f92d04e46b562f Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Wed, 31 Jul 2024 16:32:18 +0900 Subject: [PATCH 033/168] =?UTF-8?q?feat(test):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=EC=97=90=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC,=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/service/CategoryService.java | 2 +- .../codezap/template/domain/TemplateTag.java | 2 -- .../dto/response/FindTagByIdResponse.java | 12 +++++++++++ .../response/FindTemplateByIdResponse.java | 21 ++++++++++++++++++- .../repository/TemplateTagRepository.java | 5 +++++ .../template/service/TemplateService.java | 5 ++++- .../controller/TemplateControllerTest.java | 5 ++++- .../template/service/TemplateServiceTest.java | 19 ++++++++++++++++- 8 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 02a83af88..e4c59e5de 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -22,7 +22,7 @@ public CategoryService(CategoryRepository categoryRepository) { @Transactional public Long create(CreateCategoryRequest createCategoryRequest) { String categoryName = createCategoryRequest.name(); - if(categoryRepository.existsByName(categoryName)) { + if (categoryRepository.existsByName(categoryName)) { throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재하고 있습니다."); } Category category = new Category(categoryName); diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java index 0d46698ee..95214758d 100644 --- a/backend/src/main/java/codezap/template/domain/TemplateTag.java +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -14,8 +14,6 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; -import lombok.Setter; @Entity @NoArgsConstructor diff --git a/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java b/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java new file mode 100644 index 000000000..8052780b0 --- /dev/null +++ b/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java @@ -0,0 +1,12 @@ +package codezap.template.dto.response; + +import codezap.template.domain.Tag; + +public record FindTagByIdResponse( + Long id, + String name +) { + public static FindTagByIdResponse from(Tag tag) { + return new FindTagByIdResponse(tag.getId(), tag.getName()); + } +} diff --git a/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java b/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java index 6fccceb7c..734741750 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java @@ -3,25 +3,36 @@ import java.time.LocalDateTime; import java.util.List; +import codezap.category.dto.response.FindCategoryByIdResponse; import codezap.template.domain.Snippet; +import codezap.template.domain.Tag; import codezap.template.domain.Template; import io.swagger.v3.oas.annotations.media.Schema; public record FindTemplateByIdResponse( @Schema(description = "템플릿 식별자", example = "0") Long id, + @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") String title, + @Schema(description = "스니펫 목록") List snippets, + + FindCategoryByIdResponse category, + + List tags, + @Schema(description = "템플릿 수정 시간", example = "2024-11-11 12:00", type = "string") LocalDateTime modifiedAt ) { - public static FindTemplateByIdResponse of(Template template, List snippets) { + public static FindTemplateByIdResponse of(Template template, List snippets, List tags) { return new FindTemplateByIdResponse( template.getId(), template.getTitle(), mapToFindAllSnippetByTemplateResponse(snippets), + FindCategoryByIdResponse.from(template.getCategory()), + mapToFindTagByTemplateResponse(tags), template.getModifiedAt() ); } @@ -33,4 +44,12 @@ private static List mapToFindAllSnippetByTempl .map(FindAllSnippetByTemplateResponse::from) .toList(); } + + private static List mapToFindTagByTemplateResponse( + List tags + ) { + return tags.stream() + .map(FindTagByIdResponse::from) + .toList(); + } } diff --git a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java index 5396f030c..b49d7355f 100644 --- a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java @@ -1,8 +1,13 @@ package codezap.template.repository; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; +import codezap.template.domain.Template; import codezap.template.domain.TemplateTag; public interface TemplateTagRepository extends JpaRepository { + + List findAllByTemplate(Template template); } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 8adb4f6aa..538ff606d 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -78,7 +78,10 @@ public FindAllTemplatesResponse findAll() { public FindTemplateByIdResponse findById(Long id) { Template template = templateRepository.fetchById(id); List snippets = snippetRepository.findAllByTemplate(template); - return FindTemplateByIdResponse.of(template, snippets); + List tags = templateTagRepository.findAllByTemplate(template).stream() + .map(TemplateTag::getTag) + .toList(); + return FindTemplateByIdResponse.of(template, snippets, tags); } @Transactional diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index b8659010f..47d11ab65 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -191,7 +191,10 @@ void findOneTemplateSuccess() { .then().log().all() .statusCode(200) .body("title", is(templateRequest.title()), - "snippets.size()", is(2)); + "snippets.size()", is(2), + "category.id", is(1), + "category.name", is("category"), + "tags.size()", is(2)); } @Test diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 27cb2591d..10d97d771 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -18,7 +18,9 @@ import codezap.category.domain.Category; import codezap.category.repository.CategoryRepository; import codezap.template.domain.Snippet; +import codezap.template.domain.Tag; import codezap.template.domain.Template; +import codezap.template.domain.TemplateTag; import codezap.template.domain.ThumbnailSnippet; import codezap.template.dto.request.CreateSnippetRequest; import codezap.template.dto.request.CreateTemplateRequest; @@ -27,7 +29,9 @@ import codezap.template.dto.response.FindAllTemplatesResponse; import codezap.template.dto.response.FindTemplateByIdResponse; import codezap.template.repository.SnippetRepository; +import codezap.template.repository.TagRepository; import codezap.template.repository.TemplateRepository; +import codezap.template.repository.TemplateTagRepository; import codezap.template.repository.ThumbnailSnippetRepository; import io.restassured.RestAssured; @@ -50,9 +54,15 @@ class TemplateServiceTest { @Autowired private ThumbnailSnippetRepository thumbnailSnippetRepository; + @Autowired private CategoryRepository categoryRepository; + @Autowired + private TemplateTagRepository templateTagRepository; + @Autowired + private TagRepository tagRepository; + @BeforeEach void setting() { RestAssured.port = port; @@ -99,7 +109,10 @@ void findOneTemplateSuccess() { // then assertAll( () -> assertThat(foundTemplate.title()).isEqualTo(template.getTitle()), - () -> assertThat(foundTemplate.snippets()).hasSize(snippetRepository.findAllByTemplate(template).size()) + () -> assertThat(foundTemplate.snippets()).hasSize( + snippetRepository.findAllByTemplate(template).size()), + () -> assertThat(foundTemplate.category().id()).isEqualTo(template.getCategory().getId()), + () -> assertThat(foundTemplate.tags()).hasSize(2) ); } @@ -174,6 +187,10 @@ private Template saveTemplate(CreateTemplateRequest createTemplateRequest) { Snippet savedFirstSnippet = snippetRepository.save(new Snippet(savedTemplate, "filename1", "content1", 1)); snippetRepository.save(new Snippet(savedTemplate, "filename2", "content2", 2)); thumbnailSnippetRepository.save(new ThumbnailSnippet(savedTemplate, savedFirstSnippet)); + createTemplateRequest.tags().stream() + .map(Tag::new) + .map(tagRepository::save) + .forEach(tag -> templateTagRepository.save(new TemplateTag(savedTemplate, tag))); return savedTemplate; } From a36eb9a29db48932411948575e324f3ad2da2f73 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Thu, 1 Aug 2024 11:28:56 +0900 Subject: [PATCH 034/168] =?UTF-8?q?feat(template):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=EC=97=90=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=B0=8F=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/template/domain/Template.java | 3 ++- .../codezap/template/domain/TemplateTag.java | 2 +- .../dto/request/UpdateTemplateRequest.java | 6 ++++- .../template/repository/TagRepository.java | 3 +++ .../repository/TemplateTagRepository.java | 2 ++ .../template/service/TemplateService.java | 14 ++++++++++- .../controller/TemplateControllerTest.java | 14 ++++++++--- .../template/service/TemplateServiceTest.java | 25 +++++++++++++++---- 8 files changed, 56 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index bd55d4103..5bdfa6446 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -35,7 +35,8 @@ public Template(String title, Category category) { this.category = category; } - public void updateTitle(String title) { + public void updateTitle(String title, Category category) { this.title = title; + this.category = category; } } diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java index 95214758d..eefb2ca0b 100644 --- a/backend/src/main/java/codezap/template/domain/TemplateTag.java +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -48,4 +48,4 @@ public TemplateTag(Template template, Tag tag) { this.template = template; this.tag = tag; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java index 06e2cfcaf..16fd59c91 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java @@ -23,7 +23,11 @@ public record UpdateTemplateRequest( @Schema(description = "삭제한 스니펫 식별자") @NotNull(message = "deleteSnippetIds 리스트가 null 입니다.") - List deleteSnippetIds + List deleteSnippetIds, + + Long categoryId, + + List tags ) implements ValidatedSnippetsOrdinalRequest { @Override public List extractSnippetsOrdinal() { diff --git a/backend/src/main/java/codezap/template/repository/TagRepository.java b/backend/src/main/java/codezap/template/repository/TagRepository.java index e813662e8..612dee2b3 100644 --- a/backend/src/main/java/codezap/template/repository/TagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TagRepository.java @@ -5,4 +5,7 @@ import codezap.template.domain.Tag; public interface TagRepository extends JpaRepository { + boolean existsByName(String name); + + Tag findByName(String name); } diff --git a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java index b49d7355f..6b0ab4f6e 100644 --- a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java @@ -10,4 +10,6 @@ public interface TemplateTagRepository extends JpaRepository { List findAllByTemplate(Template template); + + void deleteAllByTemplate(Template template); } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 538ff606d..b610bed92 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -86,8 +86,9 @@ public FindTemplateByIdResponse findById(Long id) { @Transactional public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) { + Category category = categoryRepository.fetchById(updateTemplateRequest.categoryId()); Template template = templateRepository.fetchById(templateId); - template.updateTitle(updateTemplateRequest.title()); + template.updateTitle(updateTemplateRequest.title(), category); updateTemplateRequest.updateSnippets().forEach(this::updateSnippet); updateTemplateRequest.createSnippets() @@ -100,6 +101,17 @@ public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) } updateTemplateRequest.deleteSnippetIds().forEach(snippetRepository::deleteById); + + templateTagRepository.deleteAllByTemplate(template); + updateTemplateRequest.tags().stream() + .map(Tag::new) + .filter(tag -> !tagRepository.existsByName(tag.getName())) + .forEach(tagRepository::save); + + List tags = updateTemplateRequest.tags().stream() + .map(tagRepository::findByName) + .toList(); + tags.forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); } @Transactional diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 47d11ab65..c45ac1c18 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -217,7 +217,8 @@ class updateTemplateTest { @DisplayName("템플릿 수정 성공") void updateTemplateSuccess() { // given - categoryService.create(new CreateCategoryRequest("category")); + categoryService.create(new CreateCategoryRequest("category1")); + categoryService.create(new CreateCategoryRequest("category2")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -230,7 +231,9 @@ void updateTemplateSuccess() { List.of( new UpdateSnippetRequest(2L, "updateFilename2", "updateContent2", 1) ), - List.of(1L) + List.of(1L), + 2L, + List.of("tag1", "tag3") ); // when & then @@ -248,7 +251,8 @@ void updateTemplateSuccess() { @CsvSource({"1, 2, 1", "3, 2, 1", "0, 2, 1"}) void updateTemplateFailWithWrongSnippetOrdinal(int createOrdinal1, int createOrdinal2, int updateOrdinal) { // given - categoryService.create(new CreateCategoryRequest("category")); + categoryService.create(new CreateCategoryRequest("category1")); + categoryService.create(new CreateCategoryRequest("category2")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); templateService.create(templateRequest); @@ -261,7 +265,9 @@ void updateTemplateFailWithWrongSnippetOrdinal(int createOrdinal1, int createOrd List.of( new UpdateSnippetRequest(2L, "updateFilename2", "updateContent2", updateOrdinal) ), - List.of(1L) + List.of(1L), + 2L, + List.of("tag1", "tag3") ); // when & then diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 10d97d771..ba802e6b6 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -76,10 +76,15 @@ void createTemplateSuccess() { categoryRepository.save(new Category("category")); // when - templateService.create(createTemplateRequest); + Long id = templateService.create(createTemplateRequest); + Template template = templateRepository.fetchById(id); // then - assertThat(templateRepository.findAll()).hasSize(1); + assertAll( + () -> assertThat(templateRepository.findAll()).hasSize(1), + () -> assertThat(template.getTitle()).isEqualTo(createTemplateRequest.title()), + () -> assertThat(template.getCategory().getName()).isEqualTo("category") + ); } @Test @@ -122,18 +127,26 @@ void updateTemplateSuccess() { // given CreateTemplateRequest createdTemplate = makeTemplateRequest("title"); Template template = saveTemplate(createdTemplate); + categoryRepository.save(new Category("category2")); // when UpdateTemplateRequest updateTemplateRequest = makeUpdateTemplateRequest("updateTitle"); templateService.update(template.getId(), updateTemplateRequest); + Template updateTemplate = templateRepository.fetchById(template.getId()); List snippets = snippetRepository.findAllByTemplate(template); ThumbnailSnippet thumbnailSnippet = thumbnailSnippetRepository.findById(template.getId()).get(); + List tags = templateTagRepository.findAllByTemplate(updateTemplate).stream() + .map(TemplateTag::getTag) + .toList(); // then assertAll( - () -> assertThat(updateTemplateRequest.title()).isEqualTo("updateTitle"), + () -> assertThat(updateTemplate.getTitle()).isEqualTo("updateTitle"), () -> assertThat(thumbnailSnippet.getSnippet().getId()).isEqualTo(2L), - () -> assertThat(snippets).hasSize(3) + () -> assertThat(snippets).hasSize(3), + () -> assertThat(updateTemplate.getCategory().getId()).isEqualTo(2L), + () -> assertThat(tags).hasSize(2), + () -> assertThat(tags.get(1).getName()).isEqualTo("tag3") ); } @@ -177,7 +190,9 @@ private UpdateTemplateRequest makeUpdateTemplateRequest(String title) { List.of( new UpdateSnippetRequest(2L, "filename2", "content2", 1) ), - List.of(1L) + List.of(1L), + 2L, + List.of("tag1", "tag3") ); } From 7c6ff8772af2ab95797364e4073a42e9bc1e7682 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Thu, 1 Aug 2024 11:35:46 +0900 Subject: [PATCH 035/168] =?UTF-8?q?feat(template):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codezap/template/repository/TemplateTagRepository.java | 2 ++ .../src/main/java/codezap/template/service/TemplateService.java | 1 + 2 files changed, 3 insertions(+) diff --git a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java index 6b0ab4f6e..41fe972d3 100644 --- a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java @@ -12,4 +12,6 @@ public interface TemplateTagRepository extends JpaRepository List findAllByTemplate(Template template); void deleteAllByTemplate(Template template); + + void deleteAllByTemplateId(Long id); } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index b610bed92..e6f185b08 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -118,6 +118,7 @@ public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) public void deleteById(Long id) { thumbnailSnippetRepository.deleteByTemplateId(id); snippetRepository.deleteByTemplateId(id); + templateTagRepository.deleteAllByTemplateId(id); templateRepository.deleteById(id); } From d713868e1bc9a19680502b1fd2c496daf6992c31 Mon Sep 17 00:00:00 2001 From: zangsu Date: Thu, 1 Aug 2024 13:10:02 +0900 Subject: [PATCH 036/168] =?UTF-8?q?docs(category):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=20swagger=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 17 ++++++- .../SpringDocCategoryController.java | 49 +++++++++++++++++++ .../dto/request/CreateCategoryRequest.java | 3 ++ .../dto/request/UpdateCategoryRequest.java | 17 +++++++ .../response/FindAllCategoriesResponse.java | 2 + .../response/FindCategoryByIdResponse.java | 3 ++ 6 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java create mode 100644 backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 27e50a673..2acc801b4 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -5,19 +5,23 @@ import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.service.CategoryService; @RestController @RequestMapping("/categories") -public class CategoryController { +public class CategoryController implements SpringDocCategoryController{ private final CategoryService categoryService; @@ -35,4 +39,15 @@ public ResponseEntity createCategory(@Valid @RequestBody CreateCategoryReq public ResponseEntity getCategories() { return ResponseEntity.ok(categoryService.findAll()); } + + @PutMapping("/{id}") + public ResponseEntity updateCategory(@PathVariable Long id, + @Valid @RequestBody UpdateCategoryRequest updateCategoryRequest) { + return null; + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteCategory(@PathVariable Long id) { + return null; + } } diff --git a/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java b/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java new file mode 100644 index 000000000..621d23002 --- /dev/null +++ b/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java @@ -0,0 +1,49 @@ +package codezap.category.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.request.UpdateCategoryRequest; +import codezap.category.dto.response.FindAllCategoriesResponse; +import codezap.global.swagger.error.ApiErrorResponse; +import codezap.global.swagger.error.ErrorCase; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "카테고리 CRUD API", description = "카테고리 생성, 목록 조회, 삭제, 수정 API") +public interface SpringDocCategoryController { + + @Operation(summary = "카테고리 생성", description = """ + 새로운 카테고리를 생성합니다. \n + 새로운 카테고리의 이름이 필요합니다. \n + """) + @ApiResponse(responseCode = "201", description = "카테고리 생성 성공", headers = { + @Header(name = "생성된 카테고리의 API 경로", example = "/categories/1")}) + @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories", errorCases = { + @ErrorCase(description = "모든 필드 중 null인 값이 있는 경우", exampleMessage = "카테고리 이름이 null 입니다."), + @ErrorCase(description = "카테고리 이름이 255자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 255자까지 입력 가능합니다.") + }) + ResponseEntity createCategory(CreateCategoryRequest createCategoryRequest); + + @Operation(summary = "카테고리 목록 조회", description = "생성된 모든 카테고리를 조회합니다.") + @ApiResponse(responseCode = "200", description = "조회 성공", + content = {@Content(schema = @Schema(implementation = FindAllCategoriesResponse.class))}) + ResponseEntity getCategories(); + + @Operation(summary = "카테고리 수정", description = "해당하는 식별자의 카테고리를 수정합니다.") + @ApiResponse(responseCode = "200", description = "카테고리 수정 성공") + @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories/1", errorCases = { + @ErrorCase(description = "해당하는 id 값인 카테고리가 없는 경우", + exampleMessage = "식별자 1에 해당하는 카테고리가 존재하지 않습니다."), + }) + ResponseEntity updateCategory(Long id, UpdateCategoryRequest updateCategoryRequest); + + @Operation(summary = "카테고리 삭제", description = "해당하는 식별자의 카테고리를 삭제합니다.") + @ApiResponse(responseCode = "204", description = "카테고리 삭제 성공") + ResponseEntity deleteCategory(Long id); +} diff --git a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java index 7d1276e94..86946b8a8 100644 --- a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java +++ b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java @@ -3,7 +3,10 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import io.swagger.v3.oas.annotations.media.Schema; + public record CreateCategoryRequest( + @Schema(description = "카테고리 이름", example = "Spring") @NotNull(message = "카테고리 이름이 null 입니다.") @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.") String name diff --git a/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java new file mode 100644 index 000000000..5a8bfb467 --- /dev/null +++ b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java @@ -0,0 +1,17 @@ +package codezap.category.dto.request; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record UpdateCategoryRequest( + @Schema(description = "카테고리 식별자", example = "1") + @NotNull(message = "카테고리 id가 null 입니다.") + Long id, + @Schema(description = "카테고리 이름", example = "Spring") + @NotNull(message = "카테고리 이름이 null 입니다.") + @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.") + String name +) { +} diff --git a/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java b/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java index e7df7c5a6..b17e2168f 100644 --- a/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java +++ b/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java @@ -3,8 +3,10 @@ import java.util.List; import codezap.category.domain.Category; +import io.swagger.v3.oas.annotations.media.Schema; public record FindAllCategoriesResponse( + @Schema(description = "카테고리 목록") List categories ) { public static FindAllCategoriesResponse from(List categories) { diff --git a/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java b/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java index fe7909aec..999a7a46a 100644 --- a/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java +++ b/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java @@ -1,9 +1,12 @@ package codezap.category.dto.response; import codezap.category.domain.Category; +import io.swagger.v3.oas.annotations.media.Schema; public record FindCategoryByIdResponse( + @Schema(description = "카테고리 식별자", example = "1") Long id, + @Schema(description = "카테고리 이름", example = "Spring") String name ) { public static FindCategoryByIdResponse from(Category category) { From 1b8b6a175a30f7101ee2b77965fdc3d6462b824f Mon Sep 17 00:00:00 2001 From: zangsu Date: Thu, 1 Aug 2024 18:29:20 +0900 Subject: [PATCH 037/168] =?UTF-8?q?docs(category):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 3 +- .../codezap/category/domain/Category.java | 4 ++ .../dto/request/UpdateCategoryRequest.java | 3 - .../category/service/CategoryService.java | 11 ++++ .../controller/CategoryControllerTest.java | 61 ++++++++++++++++++- .../category/service/CategoryServiceTest.java | 16 ++++- 6 files changed, 92 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 2acc801b4..311ed556f 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -43,7 +43,8 @@ public ResponseEntity getCategories() { @PutMapping("/{id}") public ResponseEntity updateCategory(@PathVariable Long id, @Valid @RequestBody UpdateCategoryRequest updateCategoryRequest) { - return null; + categoryService.update(id, updateCategoryRequest); + return ResponseEntity.ok().build(); } @DeleteMapping("/{id}") diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index 5ffb71ed2..b44c07ddb 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -24,4 +24,8 @@ public class Category extends BaseTimeEntity { public Category(String name) { this.name = name; } + + public void updateName(String name) { + this.name = name; + } } diff --git a/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java index 5a8bfb467..4ed48bf0c 100644 --- a/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java +++ b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java @@ -6,9 +6,6 @@ import io.swagger.v3.oas.annotations.media.Schema; public record UpdateCategoryRequest( - @Schema(description = "카테고리 식별자", example = "1") - @NotNull(message = "카테고리 id가 null 입니다.") - Long id, @Schema(description = "카테고리 이름", example = "Spring") @NotNull(message = "카테고리 이름이 null 입니다.") @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.") diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index e4c59e5de..151a63945 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -6,6 +6,7 @@ import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.repository.CategoryRepository; import codezap.global.exception.CodeZapException; @@ -32,4 +33,14 @@ public Long create(CreateCategoryRequest createCategoryRequest) { public FindAllCategoriesResponse findAll() { return FindAllCategoriesResponse.from(categoryRepository.findAll()); } + + @Transactional + public void update(Long id, UpdateCategoryRequest updateCategoryRequest) { + if (categoryRepository.existsByName(updateCategoryRequest.name())) { + throw new CodeZapException(HttpStatus.CONFLICT, + "이름이 " + updateCategoryRequest.name() + "인 카테고리가 이미 존재하고 있습니다."); + } + Category category = categoryRepository.fetchById(id); + category.updateName(updateCategoryRequest.name()); + } } diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index 16f519f83..18f66462d 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -14,6 +14,7 @@ import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.service.CategoryService; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -83,4 +84,62 @@ void findAllCategoriesSuccess() { .statusCode(200) .body("categories.size()", is(2)); } -} \ No newline at end of file + + @Nested + @DisplayName("카테고리 수정 테스트") + class updateCategoryTest { + + Long savedCategoryId; + + @BeforeEach + void saveCategory() { + savedCategoryId = categoryService.create(new CreateCategoryRequest("category1")); + } + + @Test + @DisplayName("카테고리 수정 성공") + void updateCategorySuccess() { + UpdateCategoryRequest updateCategoryRequest = new UpdateCategoryRequest("a".repeat(MAX_LENGTH)); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(updateCategoryRequest) + .when().put("/categories/" + savedCategoryId) + .then().log().all() + .statusCode(200); + } + + @Test + @DisplayName("카테고리 수정 실패: 카테고리 이름 길이 초과") + void updateCategoryFailWithLongName() { + UpdateCategoryRequest updateCategoryRequest = new UpdateCategoryRequest("a".repeat(MAX_LENGTH + 1)); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(updateCategoryRequest) + .when().put("/categories/" + savedCategoryId) + .then().log().all() + .statusCode(400) + .body("detail", is("카테고리 이름은 최대 255자까지 입력 가능합니다.")); + } + + @Test + @DisplayName("카테고리 수정 실패: 중복된 이름의 카테고리 존재") + void updateCategoryFailWithDuplicatedName() { + //given + String duplicatedName = "duplicatedName"; + categoryService.create(new CreateCategoryRequest(duplicatedName)); + + UpdateCategoryRequest createCategoryRequest = new UpdateCategoryRequest(duplicatedName); + + //when & then + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(createCategoryRequest) + .when().put("/categories/" + savedCategoryId) + .then().log().all() + .statusCode(409) + .body("detail", is("이름이 " + duplicatedName + "인 카테고리가 이미 존재하고 있습니다.")); + } + } +} diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index 944a184af..6dab5569f 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -16,6 +16,7 @@ import codezap.category.domain.Category; import codezap.category.dto.request.CreateCategoryRequest; +import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.repository.CategoryRepository; import codezap.global.exception.CodeZapException; @@ -75,4 +76,17 @@ void findAllCategoriesSuccess() { assertThat(findAllCategoriesResponse.categories()).hasSize(2); } -} \ No newline at end of file + + @Test + @DisplayName("카테고리 수정 성공") + void updateCategorySuccess() { + //given + Category savedCategory = categoryRepository.save(new Category("category1")); + + //when + categoryService.update(savedCategory.getId(), new UpdateCategoryRequest("updateName")); + + //then + assertThat(categoryRepository.fetchById(savedCategory.getId()).getName()).isEqualTo("updateName"); + } +} From 03eb850ba5b82df480c6643cfcde43bea263fd24 Mon Sep 17 00:00:00 2001 From: zangsu Date: Thu, 1 Aug 2024 18:30:00 +0900 Subject: [PATCH 038/168] =?UTF-8?q?refactor(controller):=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20=EB=B0=8F=20DisplayName=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/category/controller/CategoryControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index 18f66462d..b80149a6e 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -55,8 +55,8 @@ void createCategorySuccess() { } @Test - @DisplayName("카테고리 생성 실패:") - void createCategoryFail() { + @DisplayName("카테고리 생성 실패: 카테고리 이름 길이 초과") + void createCategoryFailWithLongName() { CreateCategoryRequest createCategoryRequest = new CreateCategoryRequest("a".repeat(MAX_LENGTH + 1)); RestAssured.given().log().all() From ba022159424b9e7aed8456d788ee6d86adbd668f Mon Sep 17 00:00:00 2001 From: zangsu Date: Thu, 1 Aug 2024 20:55:24 +0900 Subject: [PATCH 039/168] =?UTF-8?q?feat(category):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 4 +- .../category/service/CategoryService.java | 27 ++++++--- .../repository/TemplateRepository.java | 2 + .../controller/CategoryControllerTest.java | 57 +++++++++++++++++++ .../category/service/CategoryServiceTest.java | 13 +++++ 5 files changed, 94 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 311ed556f..ee6179d34 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -49,6 +49,8 @@ public ResponseEntity updateCategory(@PathVariable Long id, @DeleteMapping("/{id}") public ResponseEntity deleteCategory(@PathVariable Long id) { - return null; + categoryService.deleteById(id); + return ResponseEntity.noContent() + .build(); } } diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 151a63945..0e84c0801 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -10,22 +10,23 @@ import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.repository.CategoryRepository; import codezap.global.exception.CodeZapException; +import codezap.template.repository.TemplateRepository; @Service public class CategoryService { private final CategoryRepository categoryRepository; + private final TemplateRepository templateRepository; - public CategoryService(CategoryRepository categoryRepository) { + public CategoryService(CategoryRepository categoryRepository, TemplateRepository templateRepository) { this.categoryRepository = categoryRepository; + this.templateRepository = templateRepository; } @Transactional public Long create(CreateCategoryRequest createCategoryRequest) { String categoryName = createCategoryRequest.name(); - if (categoryRepository.existsByName(categoryName)) { - throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재하고 있습니다."); - } + validateDuplicatedCategory(categoryName); Category category = new Category(categoryName); return categoryRepository.save(category).getId(); } @@ -36,11 +37,21 @@ public FindAllCategoriesResponse findAll() { @Transactional public void update(Long id, UpdateCategoryRequest updateCategoryRequest) { - if (categoryRepository.existsByName(updateCategoryRequest.name())) { - throw new CodeZapException(HttpStatus.CONFLICT, - "이름이 " + updateCategoryRequest.name() + "인 카테고리가 이미 존재하고 있습니다."); - } + validateDuplicatedCategory(updateCategoryRequest.name()); Category category = categoryRepository.fetchById(id); category.updateName(updateCategoryRequest.name()); } + + public void deleteById(Long id) { + if (templateRepository.existsByCategoryId(id)) { + throw new CodeZapException(HttpStatus.BAD_REQUEST, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다."); + } + categoryRepository.deleteById(id); + } + + private void validateDuplicatedCategory(String categoryName) { + if (categoryRepository.existsByName(categoryName)) { + throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재하고 있습니다."); + } + } } diff --git a/backend/src/main/java/codezap/template/repository/TemplateRepository.java b/backend/src/main/java/codezap/template/repository/TemplateRepository.java index 47b00b246..127eef4cf 100644 --- a/backend/src/main/java/codezap/template/repository/TemplateRepository.java +++ b/backend/src/main/java/codezap/template/repository/TemplateRepository.java @@ -12,4 +12,6 @@ default Template fetchById(Long id) { return findById(id).orElseThrow( () -> new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + id + "에 해당하는 템플릿이 존재하지 않습니다.")); } + + boolean existsByCategoryId(Long categoryId); } diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index b80149a6e..ec3d83feb 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -2,6 +2,8 @@ import static org.hamcrest.Matchers.is; +import java.util.List; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -16,6 +18,9 @@ import codezap.category.dto.request.CreateCategoryRequest; import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.service.CategoryService; +import codezap.template.dto.request.CreateSnippetRequest; +import codezap.template.dto.request.CreateTemplateRequest; +import codezap.template.service.TemplateService; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -31,6 +36,8 @@ class CategoryControllerTest { @Autowired private CategoryService categoryService; + @Autowired + private TemplateService templateService; @BeforeEach void setting() { @@ -142,4 +149,54 @@ void updateCategoryFailWithDuplicatedName() { .body("detail", is("이름이 " + duplicatedName + "인 카테고리가 이미 존재하고 있습니다.")); } } + + + @Nested + @DisplayName("카테고리 삭제 테스트") + class deleteCategoryTest { + + Long savedCategoryId; + + @BeforeEach + void saveCategory() { + savedCategoryId = categoryService.create(new CreateCategoryRequest("category1")); + } + + @Test + @DisplayName("카테고리 삭제 성공") + void deleteCategorySuccess() { + RestAssured.given().log().all() + .when().delete("/categories/" + savedCategoryId) + .then().log().all() + .statusCode(204); + } + + @Test + @DisplayName("카테고리 수정 성공: 존재하지 않는 카테고리의 삭제 요청") + void updateCategoryFailWithDuplicatedName() { + RestAssured.given().log().all() + .when().delete("/categories/" + savedCategoryId + 1) + .then().log().all() + .statusCode(204); + } + + @Test + @DisplayName("카테고리 삭제 실패: 템플릿이 존재하는 카테고리는 삭제 불가능") + void updateCategoryFailWithLongName() { + //given + templateService.create(new CreateTemplateRequest( + "title", + List.of(new CreateSnippetRequest("filename", "content", 1)), + savedCategoryId, + List.of("tag1", "tag2") + )); + + //when & then + RestAssured.given().log().all() + .when().delete("/categories/" + savedCategoryId) + .then().log().all() + .statusCode(400) + .body("detail", is("템플릿이 존재하는 카테고리는 삭제할 수 없습니다.")); + } + } } diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index 6dab5569f..d80d23413 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -89,4 +89,17 @@ void updateCategorySuccess() { //then assertThat(categoryRepository.fetchById(savedCategory.getId()).getName()).isEqualTo("updateName"); } + + @Test + @DisplayName("카테고리 삭제 성공") + void deleteCategorySuccess() { + //given + Category savedCategory = categoryRepository.save(new Category("category1")); + + //when + categoryService.deleteById(savedCategory.getId()); + + //then + assertThat(categoryRepository.findById(savedCategory.getId())).isEmpty(); + } } From ebab110693bb1b6694259a07e15f8e47c8c384f7 Mon Sep 17 00:00:00 2001 From: zangsu Date: Thu, 1 Aug 2024 21:01:55 +0900 Subject: [PATCH 040/168] =?UTF-8?q?docs(category):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 존재하고 있습니다 -> 존재합니다 로 변경 - 응답에 중복된 이름에 대한 예외 추가 --- .../controller/SpringDocCategoryController.java | 10 +++++++++- .../java/codezap/category/service/CategoryService.java | 2 +- .../category/controller/CategoryControllerTest.java | 2 +- .../codezap/category/service/CategoryServiceTest.java | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java b/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java index 621d23002..f3ad05bdf 100644 --- a/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java +++ b/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java @@ -26,7 +26,9 @@ public interface SpringDocCategoryController { @Header(name = "생성된 카테고리의 API 경로", example = "/categories/1")}) @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories", errorCases = { @ErrorCase(description = "모든 필드 중 null인 값이 있는 경우", exampleMessage = "카테고리 이름이 null 입니다."), - @ErrorCase(description = "카테고리 이름이 255자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 255자까지 입력 가능합니다.") + @ErrorCase(description = "카테고리 이름이 255자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 255자까지 입력 가능합니다."), + @ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", + exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다.") }) ResponseEntity createCategory(CreateCategoryRequest createCategoryRequest); @@ -40,10 +42,16 @@ public interface SpringDocCategoryController { @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories/1", errorCases = { @ErrorCase(description = "해당하는 id 값인 카테고리가 없는 경우", exampleMessage = "식별자 1에 해당하는 카테고리가 존재하지 않습니다."), + @ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", + exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다.") }) ResponseEntity updateCategory(Long id, UpdateCategoryRequest updateCategoryRequest); @Operation(summary = "카테고리 삭제", description = "해당하는 식별자의 카테고리를 삭제합니다.") @ApiResponse(responseCode = "204", description = "카테고리 삭제 성공") + @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories/1", errorCases = { + @ErrorCase(description = "삭제하려는 카테고리에 템플릿이 존재하는 경우", + exampleMessage = "템플릿이 존재하는 카테고리는 삭제할 수 없습니다."), + }) ResponseEntity deleteCategory(Long id); } diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 0e84c0801..48a9f5191 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -51,7 +51,7 @@ public void deleteById(Long id) { private void validateDuplicatedCategory(String categoryName) { if (categoryRepository.existsByName(categoryName)) { - throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재하고 있습니다."); + throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재합니다."); } } } diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index ec3d83feb..71b21afd3 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -146,7 +146,7 @@ void updateCategoryFailWithDuplicatedName() { .when().put("/categories/" + savedCategoryId) .then().log().all() .statusCode(409) - .body("detail", is("이름이 " + duplicatedName + "인 카테고리가 이미 존재하고 있습니다.")); + .body("detail", is("이름이 " + duplicatedName + "인 카테고리가 이미 존재합니다.")); } } diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index d80d23413..11a811216 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -62,7 +62,7 @@ void createCategoryFailWithDuplicateName() { assertThatThrownBy(() -> categoryService.create(createCategoryRequest)) .isInstanceOf(CodeZapException.class) - .hasMessage("이름이 " + createCategoryRequest.name() + "인 카테고리가 이미 존재하고 있습니다."); + .hasMessage("이름이 " + createCategoryRequest.name() + "인 카테고리가 이미 존재합니다."); } } From c087ac389e05dd6cdf2a1a09fd77b9f6dc5ebf72 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Thu, 1 Aug 2024 16:41:10 +0900 Subject: [PATCH 041/168] =?UTF-8?q?feat(template):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20dto=EC=97=90=20description=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/template/domain/Template.java | 6 +++-- .../dto/request/CreateTemplateRequest.java | 2 ++ .../dto/request/UpdateTemplateRequest.java | 2 ++ .../response/FindTemplateByIdResponse.java | 3 +++ .../template/service/TemplateService.java | 10 ++++++-- .../controller/TemplateControllerTest.java | 25 ++++++++++++++----- .../repository/SnippetRepositoryTest.java | 4 +-- .../template/service/TemplateServiceTest.java | 6 +++-- 8 files changed, 44 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index 5bdfa6446..75aff1906 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -30,13 +30,15 @@ public class Template extends BaseTimeEntity { @ManyToOne(optional = false) private Category category; - public Template(String title, Category category) { + public Template(String title, String description, Category category) { this.title = title; + this.description = description; this.category = category; } - public void updateTitle(String title, Category category) { + public void updateTemplate(String title, String description, Category category) { this.title = title; + this.description = description; this.category = category; } } diff --git a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java index 9527ff657..4b9fac6fd 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java @@ -15,6 +15,8 @@ public record CreateTemplateRequest( @Size(max = 255, message = "템플릿 이름은 최대 255자까지 입력 가능합니다.") String title, + String description, + @Schema(description = "템플릿의 스니펫 내역") @NotNull(message = "스니펫 리스트가 null 입니다.") @Valid diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java index 16fd59c91..25860a00f 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java @@ -13,6 +13,8 @@ public record UpdateTemplateRequest( @NotNull(message = "템플릿 이름이 null 입니다.") String title, + String description, + @Schema(description = "새로 추가한 스니펫 내역") @NotNull(message = "createSnippets 리스트가 null 입니다.") List createSnippets, diff --git a/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java b/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java index 734741750..272b2c60b 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java @@ -16,6 +16,8 @@ public record FindTemplateByIdResponse( @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") String title, + String description, + @Schema(description = "스니펫 목록") List snippets, @@ -30,6 +32,7 @@ public static FindTemplateByIdResponse of(Template template, List snipp return new FindTemplateByIdResponse( template.getId(), template.getTitle(), + template.getDescription(), mapToFindAllSnippetByTemplateResponse(snippets), FindCategoryByIdResponse.from(template.getCategory()), mapToFindTagByTemplateResponse(tags), diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index e6f185b08..d8f2e6fd3 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -54,7 +54,13 @@ public TemplateService(ThumbnailSnippetRepository thumbnailSnippetRepository, public Long create(CreateTemplateRequest createTemplateRequest) { Category category = categoryRepository.fetchById(createTemplateRequest.categoryId()); - Template template = templateRepository.save(new Template(createTemplateRequest.title(), category)); + Template template = templateRepository.save( + new Template( + createTemplateRequest.title(), + createTemplateRequest.description(), + category + ) + ); List tags = createTemplateRequest.tags().stream() .map(Tag::new) @@ -88,7 +94,7 @@ public FindTemplateByIdResponse findById(Long id) { public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) { Category category = categoryRepository.fetchById(updateTemplateRequest.categoryId()); Template template = templateRepository.fetchById(templateId); - template.updateTitle(updateTemplateRequest.title(), category); + template.updateTemplate(updateTemplateRequest.title(), updateTemplateRequest.description(), category); updateTemplateRequest.updateSnippets().forEach(this::updateSnippet); updateTemplateRequest.createSnippets() diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index c45ac1c18..43f56fa3a 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -58,7 +58,9 @@ class createTemplateTest { void createTemplateSuccess(String repeatTarget, int maxLength) { String maxTitle = "a".repeat(MAX_LENGTH); categoryService.create(new CreateCategoryRequest("category")); - CreateTemplateRequest templateRequest = new CreateTemplateRequest(maxTitle, + CreateTemplateRequest templateRequest = new CreateTemplateRequest( + maxTitle, + "description", List.of(new CreateSnippetRequest("a".repeat(MAX_LENGTH), repeatTarget.repeat(maxLength), 1)), 1L, List.of("tag1", "tag2") @@ -78,7 +80,9 @@ void createTemplateSuccess(String repeatTarget, int maxLength) { void createTemplateFailWithLongTitle() { String exceededTitle = "a".repeat(MAX_LENGTH + 1); categoryService.create(new CreateCategoryRequest("category")); - CreateTemplateRequest templateRequest = new CreateTemplateRequest(exceededTitle, + CreateTemplateRequest templateRequest = new CreateTemplateRequest( + exceededTitle, + "description", List.of(new CreateSnippetRequest("a", "content", 1)), 1L, List.of("tag1", "tag2") @@ -98,7 +102,9 @@ void createTemplateFailWithLongTitle() { void createTemplateFailWithLongFileName() { String exceededTitle = "a".repeat(MAX_LENGTH + 1); categoryService.create(new CreateCategoryRequest("category")); - CreateTemplateRequest templateRequest = new CreateTemplateRequest("title", + CreateTemplateRequest templateRequest = new CreateTemplateRequest( + "title", + "description", List.of(new CreateSnippetRequest(exceededTitle, "content", 1)), 1L, List.of("tag1", "tag2") @@ -118,7 +124,9 @@ void createTemplateFailWithLongFileName() { @CsvSource({"a, 65536", "ㄱ, 21846"}) void createTemplateFailWithLongContent(String repeatTarget, int exceededLength) { categoryService.create(new CreateCategoryRequest("category")); - CreateTemplateRequest templateRequest = new CreateTemplateRequest("title", + CreateTemplateRequest templateRequest = new CreateTemplateRequest( + "title", + "description", List.of(new CreateSnippetRequest("title", repeatTarget.repeat(exceededLength), 1)), 1L, List.of("tag1", "tag2") @@ -138,7 +146,9 @@ void createTemplateFailWithLongContent(String repeatTarget, int exceededLength) @CsvSource({"0, 1", "1, 3", "2, 1"}) void createTemplateFailWithWrongSnippetOrdinal(int firstIndex, int secondIndex) { categoryService.create(new CreateCategoryRequest("category")); - CreateTemplateRequest templateRequest = new CreateTemplateRequest("title", + CreateTemplateRequest templateRequest = new CreateTemplateRequest( + "title", + "description", List.of(new CreateSnippetRequest("title", "content", firstIndex), new CreateSnippetRequest("title", "content", secondIndex)), 1L, @@ -159,9 +169,9 @@ void createTemplateFailWithWrongSnippetOrdinal(int firstIndex, int secondIndex) @DisplayName("템플릿 전체 조회 성공") void findAllTemplatesSuccess() { // given + categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest1 = createTemplateRequestWithTwoSnippets("title1"); CreateTemplateRequest templateRequest2 = createTemplateRequestWithTwoSnippets("title2"); - categoryService.create(new CreateCategoryRequest("category")); templateService.create(templateRequest1); templateService.create(templateRequest2); @@ -224,6 +234,7 @@ void updateTemplateSuccess() { UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( "updateTitle", + "description", List.of( new CreateSnippetRequest("filename3", "content3", 2), new CreateSnippetRequest("filename4", "content4", 3) @@ -258,6 +269,7 @@ void updateTemplateFailWithWrongSnippetOrdinal(int createOrdinal1, int createOrd UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( "updateTitle", + "description", List.of( new CreateSnippetRequest("filename3", "content3", createOrdinal1), new CreateSnippetRequest("filename4", "content4", createOrdinal2) @@ -315,6 +327,7 @@ void deleteTemplateSuccessWithNotFoundTemplate() { private static CreateTemplateRequest createTemplateRequestWithTwoSnippets(String title) { CreateTemplateRequest templateRequest = new CreateTemplateRequest( title, + "description", List.of( new CreateSnippetRequest("filename1", "content1", 1), new CreateSnippetRequest("filename2", "content2", 2) diff --git a/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java b/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java index 075ee12c7..5ce655dad 100644 --- a/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java +++ b/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java @@ -35,7 +35,7 @@ class SnippetRepositoryTest { @DisplayName("단일 스니펫 찾기 성공: 템플릿과 순서") void findOneSnippetSuccessWithTemplateAndOrdinal() { Category category = categoryRepository.save(new Category("category")); - Template template = templateRepository.save(new Template("title", category)); + Template template = templateRepository.save(new Template("title", "description", category)); Snippet snippet1 = snippetRepository.save(new Snippet(template, "filename1", "content1", 1)); Snippet snippet2 = snippetRepository.save(new Snippet(template, "filename2", "content2", 2)); @@ -53,7 +53,7 @@ void findOneSnippetSuccessWithTemplateAndOrdinal() { @DisplayName("스니펫 리스트 찾기 성공: 템플릿과 순서") void findSnippetsSuccessWithTemplateAndOrdinal() { Category category = categoryRepository.save(new Category("category")); - Template template = templateRepository.save(new Template("title", category)); + Template template = templateRepository.save(new Template("title","description", category)); Snippet snippet1 = snippetRepository.save(new Snippet(template, "filename1", "content1", 1)); Snippet snippet2 = snippetRepository.save(new Snippet(template, "filename2", "content2", 2)); Snippet snippet3 = snippetRepository.save(new Snippet(template, "filename3", "content3", 2)); diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index ba802e6b6..1e5bd7b18 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -72,8 +72,8 @@ void setting() { @DisplayName("템플릿 생성 성공") void createTemplateSuccess() { // given - CreateTemplateRequest createTemplateRequest = makeTemplateRequest("title"); categoryRepository.save(new Category("category")); + CreateTemplateRequest createTemplateRequest = makeTemplateRequest("title"); // when Long id = templateService.create(createTemplateRequest); @@ -171,6 +171,7 @@ void deleteTemplateSuccess() { private CreateTemplateRequest makeTemplateRequest(String title) { return new CreateTemplateRequest( title, + "description", List.of( new CreateSnippetRequest("filename1", "content1", 1), new CreateSnippetRequest("filename2", "content2", 2) @@ -183,6 +184,7 @@ private CreateTemplateRequest makeTemplateRequest(String title) { private UpdateTemplateRequest makeUpdateTemplateRequest(String title) { return new UpdateTemplateRequest( title, + "description", List.of( new CreateSnippetRequest("filename3", "content3", 2), new CreateSnippetRequest("filename4", "content4", 3) @@ -198,7 +200,7 @@ private UpdateTemplateRequest makeUpdateTemplateRequest(String title) { private Template saveTemplate(CreateTemplateRequest createTemplateRequest) { Category category = categoryRepository.save(new Category("category")); - Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title(), category)); + Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title(), createTemplateRequest.description(), category)); Snippet savedFirstSnippet = snippetRepository.save(new Snippet(savedTemplate, "filename1", "content1", 1)); snippetRepository.save(new Snippet(savedTemplate, "filename2", "content2", 2)); thumbnailSnippetRepository.save(new ThumbnailSnippet(savedTemplate, savedFirstSnippet)); From 5ecab426fd4413451080a6ef6e01476df0642100 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Thu, 1 Aug 2024 16:41:37 +0900 Subject: [PATCH 042/168] =?UTF-8?q?feat(cors):=20cors=20=ED=97=A4=EB=8D=94?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/codezap/global/cors/WebCorsConfiguration.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/global/cors/WebCorsConfiguration.java b/backend/src/main/java/codezap/global/cors/WebCorsConfiguration.java index b69d3863d..d451e9840 100644 --- a/backend/src/main/java/codezap/global/cors/WebCorsConfiguration.java +++ b/backend/src/main/java/codezap/global/cors/WebCorsConfiguration.java @@ -10,6 +10,7 @@ public class WebCorsConfiguration implements WebMvcConfigurer { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") - .allowedMethods("*"); + .allowedMethods("*") + .exposedHeaders("*"); } } From 96e8aae607acb958c26d26ac42e8ddc381b0cf7b Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Fri, 2 Aug 2024 11:56:28 +0900 Subject: [PATCH 043/168] =?UTF-8?q?docs(codezap):=20template=20=EB=B0=8F?= =?UTF-8?q?=20category=20=EB=AC=B8=EC=84=9C=ED=99=94=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template/controller/SpringDocTemplateController.java | 4 ++-- .../codezap/template/dto/request/CreateTemplateRequest.java | 3 +++ .../codezap/template/dto/request/UpdateTemplateRequest.java | 3 +++ .../dto/response/FindAllSnippetByTemplateResponse.java | 3 +++ .../codezap/template/dto/response/FindTagByIdResponse.java | 4 ++++ .../template/dto/response/FindTemplateByIdResponse.java | 3 +++ .../template/dto/response/FindThumbnailSnippetResponse.java | 1 + .../codezap/category/controller/CategoryControllerTest.java | 1 + 8 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java index 99f1a6bdb..f125a25f6 100644 --- a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java +++ b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java @@ -21,7 +21,7 @@ public interface SpringDocTemplateController { @Operation(summary = "템플릿 생성", description = """ 새로운 템플릿을 생성합니다. \n - 템플릿의 제목, 썸네일 스니펫의 순서, 스니펫 목록이 필요합니다. \n + 템플릿의 제목, 썸네일 스니펫의 순서, 스니펫 목록, 카테고리 ID, 태그 목록이 필요합니다. \n 스니펫 목록은 파일 이름, 소스 코드, 해당 스니펫의 순서가 필요합니다. \n * 썸네일 스니펫은 1로 고정입니다. (2024.07.15 기준) \n * 모든 스니펫 순서는 1부터 시작합니다. \n @@ -31,7 +31,7 @@ public interface SpringDocTemplateController { @Header(name = "생성된 템플릿의 API 경로", example = "/templates/1")}) @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/templates/1", errorCases = { @ErrorCase(description = "모든 필드 중 null인 값이 있는 경우", exampleMessage = "템플릿 이름 null 입니다."), - @ErrorCase(description = "제목 또는 스니펫 파일명이 255자를 초과한 경우", exampleMessage = "제목은 최대 255자까지 입력 가능합니다."), + @ErrorCase(description = "제목 또는 스니펫 파일 또는 태그 이름이 255자를 초과한 경우", exampleMessage = "제목은 최대 255자까지 입력 가능합니다."), @ErrorCase(description = "썸네일 스니펫의 순서가 1이 아닌 경우", exampleMessage = "썸네일 스니펫의 순서가 잘못되었습니다."), @ErrorCase(description = "스니펫 순서가 잘못된 경우", exampleMessage = "스니펫 순서가 잘못되었습니다."), @ErrorCase(description = "스니펫 내용 65,535 byte를 초과한 경우", exampleMessage = "파일 내용은 최대 65,535 byte까지 입력 가능합니다.") diff --git a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java index 4b9fac6fd..b9216a053 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java @@ -15,6 +15,7 @@ public record CreateTemplateRequest( @Size(max = 255, message = "템플릿 이름은 최대 255자까지 입력 가능합니다.") String title, + @Schema(description = "템플릿 설명", example = "JWT를 사용하여 로그인 기능을 구현함") String description, @Schema(description = "템플릿의 스니펫 내역") @@ -22,8 +23,10 @@ public record CreateTemplateRequest( @Valid List snippets, + @Schema(description = "카테고리 ID", example = "1") Long categoryId, + @Schema(description = "태그 리스트") List tags ) implements ValidatedSnippetsOrdinalRequest { @Override diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java index 25860a00f..b24948a7a 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java @@ -13,6 +13,7 @@ public record UpdateTemplateRequest( @NotNull(message = "템플릿 이름이 null 입니다.") String title, + @Schema(description = "템플릿 설명", example = "JWT를 사용하여 로그인 기능을 구현함") String description, @Schema(description = "새로 추가한 스니펫 내역") @@ -27,8 +28,10 @@ public record UpdateTemplateRequest( @NotNull(message = "deleteSnippetIds 리스트가 null 입니다.") List deleteSnippetIds, + @Schema(description = "카테고리 ID", example = "1") Long categoryId, + @Schema(description = "태그 리스트") List tags ) implements ValidatedSnippetsOrdinalRequest { @Override diff --git a/backend/src/main/java/codezap/template/dto/response/FindAllSnippetByTemplateResponse.java b/backend/src/main/java/codezap/template/dto/response/FindAllSnippetByTemplateResponse.java index 34815a2f7..d2520a934 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindAllSnippetByTemplateResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindAllSnippetByTemplateResponse.java @@ -6,10 +6,13 @@ public record FindAllSnippetByTemplateResponse( @Schema(description = "파일 식별자", example = "0") Long id, + @Schema(description = "파일 이름", example = "Main.java") String filename, + @Schema(description = "소스 코드", example = "public class Main { // ...") String content, + @Schema(description = "스니펫의 순서", example = "1") int ordinal ) { diff --git a/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java b/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java index 8052780b0..60d6a9173 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java @@ -1,9 +1,13 @@ package codezap.template.dto.response; import codezap.template.domain.Tag; +import io.swagger.v3.oas.annotations.media.Schema; public record FindTagByIdResponse( + @Schema(description = "태그 식별자", example = "1") Long id, + + @Schema(description = "태그 이름", example = "스프링") String name ) { public static FindTagByIdResponse from(Tag tag) { diff --git a/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java b/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java index 272b2c60b..b9451b777 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java @@ -16,13 +16,16 @@ public record FindTemplateByIdResponse( @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") String title, + @Schema(description = "템플릿 설명", example = "JWT를 사용하여 로그인 기능을 구현함") String description, @Schema(description = "스니펫 목록") List snippets, + @Schema(description = "카테고리 정보") FindCategoryByIdResponse category, + @Schema(description = "태그 목록") List tags, @Schema(description = "템플릿 수정 시간", example = "2024-11-11 12:00", type = "string") diff --git a/backend/src/main/java/codezap/template/dto/response/FindThumbnailSnippetResponse.java b/backend/src/main/java/codezap/template/dto/response/FindThumbnailSnippetResponse.java index 1cdf7303e..de47fbe4f 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindThumbnailSnippetResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindThumbnailSnippetResponse.java @@ -6,6 +6,7 @@ public record FindThumbnailSnippetResponse( @Schema(description = "파일 이름", example = "Main.java") String filename, + @Schema(description = "목록 조회 시 보여질 코드", example = "public class Main { // ...") String thumbnailContent ) { diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index 71b21afd3..0f79d6da4 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -186,6 +186,7 @@ void updateCategoryFailWithLongName() { //given templateService.create(new CreateTemplateRequest( "title", + "description", List.of(new CreateSnippetRequest("filename", "content", 1)), savedCategoryId, List.of("tag1", "tag2") From bc2fc49977da4430fe49b19caafc9b97b1b8d9f0 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Fri, 2 Aug 2024 14:04:17 +0900 Subject: [PATCH 044/168] =?UTF-8?q?refactor(template):=20dto=20=EC=A0=9C?= =?UTF-8?q?=EC=95=BD=20=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/CreateTemplateRequest.java | 5 + .../dto/request/UpdateTemplateRequest.java | 10 ++ .../controller/TemplateControllerTest.java | 164 +++++++++++++++++- 3 files changed, 178 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java index b9216a053..b7e217fb0 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java @@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import codezap.global.validation.ByteLength; import codezap.template.dto.request.validation.ValidatedSnippetsOrdinalRequest; import io.swagger.v3.oas.annotations.media.Schema; @@ -16,6 +17,8 @@ public record CreateTemplateRequest( String title, @Schema(description = "템플릿 설명", example = "JWT를 사용하여 로그인 기능을 구현함") + @NotNull(message = "템플릿 설명이 null 입니다.") + @ByteLength(max = 65_535, message = "템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.") String description, @Schema(description = "템플릿의 스니펫 내역") @@ -24,9 +27,11 @@ public record CreateTemplateRequest( List snippets, @Schema(description = "카테고리 ID", example = "1") + @NotNull(message = "카테고리 id가 null 입니다.") Long categoryId, @Schema(description = "태그 리스트") + @NotNull(message = "태그 리스트가 null 입니다.") List tags ) implements ValidatedSnippetsOrdinalRequest { @Override diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java index b24948a7a..52ebfbcaf 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java @@ -3,25 +3,33 @@ import java.util.List; import java.util.stream.Stream; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import codezap.global.validation.ByteLength; import codezap.template.dto.request.validation.ValidatedSnippetsOrdinalRequest; import io.swagger.v3.oas.annotations.media.Schema; public record UpdateTemplateRequest( @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") @NotNull(message = "템플릿 이름이 null 입니다.") + @Size(max = 255, message = "템플릿 이름은 최대 255자까지 입력 가능합니다.") String title, @Schema(description = "템플릿 설명", example = "JWT를 사용하여 로그인 기능을 구현함") + @NotNull(message = "템플릿 설명이 null 입니다.") + @ByteLength(max = 65_535, message = "템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.") String description, @Schema(description = "새로 추가한 스니펫 내역") @NotNull(message = "createSnippets 리스트가 null 입니다.") + @Valid List createSnippets, @Schema(description = "삭제, 생성 스니펫을 제외한 모든 스니펫 내역") @NotNull(message = "updateSnippets 리스트가 null 입니다.") + @Valid List updateSnippets, @Schema(description = "삭제한 스니펫 식별자") @@ -29,9 +37,11 @@ public record UpdateTemplateRequest( List deleteSnippetIds, @Schema(description = "카테고리 ID", example = "1") + @NotNull(message = "카테고리 id가 null 입니다.") Long categoryId, @Schema(description = "태그 리스트") + @NotNull(message = "태그 리스트가 null 입니다.") List tags ) implements ValidatedSnippetsOrdinalRequest { @Override diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 43f56fa3a..ee5abca0e 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -60,7 +60,7 @@ void createTemplateSuccess(String repeatTarget, int maxLength) { categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = new CreateTemplateRequest( maxTitle, - "description", + repeatTarget.repeat(maxLength), List.of(new CreateSnippetRequest("a".repeat(MAX_LENGTH), repeatTarget.repeat(maxLength), 1)), 1L, List.of("tag1", "tag2") @@ -141,6 +141,28 @@ void createTemplateFailWithLongContent(String repeatTarget, int exceededLength) .body("detail", is("파일 내용은 최대 65,535 Byte까지 입력 가능합니다.")); } + @ParameterizedTest + @DisplayName("템플릿 생성 실패: 템플릿 설명 길이 초과") + @CsvSource({"a, 65536", "ㄱ, 21846"}) + void createTemplateFailWithLongDescription(String repeatTarget, int exceededLength) { + categoryService.create(new CreateCategoryRequest("category")); + CreateTemplateRequest templateRequest = new CreateTemplateRequest( + "title", + repeatTarget.repeat(exceededLength), + List.of(new CreateSnippetRequest("title", "content", 1)), + 1L, + List.of("tag1", "tag2") + ); + + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(templateRequest) + .when().post("/templates") + .then().log().all() + .statusCode(400) + .body("detail", is("템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.")); + } + @ParameterizedTest @DisplayName("템플릿 생성 실패: 잘못된 스니펫 순서 입력") @CsvSource({"0, 1", "1, 3", "2, 1"}) @@ -256,6 +278,146 @@ void updateTemplateSuccess() { .statusCode(200); } + @Test + @DisplayName("템플릿 수정 실패: 템플릿 이름 길이 초과") + void updateTemplateFailWithLongName() { + // given + String exceededTitle = "a".repeat(MAX_LENGTH + 1); + categoryService.create(new CreateCategoryRequest("category1")); + categoryService.create(new CreateCategoryRequest("category2")); + CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); + templateService.create(templateRequest); + + UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( + exceededTitle, + "description", + List.of( + new CreateSnippetRequest("filename3", "content3", 2), + new CreateSnippetRequest("filename4", "content4", 3) + ), + List.of( + new UpdateSnippetRequest(2L, "updateFilename2", "updateContent2", 1) + ), + List.of(1L), + 2L, + List.of("tag1", "tag3") + ); + + // when & then + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(updateTemplateRequest) + .when().post("/templates/1") + .then().log().all() + .statusCode(400) + .body("detail", is("템플릿 이름은 최대 255자까지 입력 가능합니다.")); + } + + @Test + @DisplayName("템플릿 생성 실패: 파일 이름 길이 초과") + void updateTemplateFailWithLongFileName() { + // given + String exceededTitle = "a".repeat(MAX_LENGTH + 1); + categoryService.create(new CreateCategoryRequest("category1")); + categoryService.create(new CreateCategoryRequest("category2")); + CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); + templateService.create(templateRequest); + + UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( + "title", + "description", + List.of( + new CreateSnippetRequest(exceededTitle, "content3", 2), + new CreateSnippetRequest("filename4", "content4", 3) + ), + List.of( + new UpdateSnippetRequest(2L, "updateFilename2", "updateContent2", 1) + ), + List.of(1L), + 2L, + List.of("tag1", "tag3") + ); + + // when & then + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(updateTemplateRequest) + .when().post("/templates/1") + .then().log().all() + .statusCode(400) + .body("detail", is("파일 이름은 최대 255자까지 입력 가능합니다.")); + } + + @ParameterizedTest + @DisplayName("템플릿 생성 실패: 파일 내용 길이 초과") + @CsvSource({"a, 65536", "ㄱ, 21846"}) + void updateTemplateFailWithLongFileContent(String repeatTarget, int exceedLength) { + // given + categoryService.create(new CreateCategoryRequest("category1")); + categoryService.create(new CreateCategoryRequest("category2")); + CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); + templateService.create(templateRequest); + + UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( + "title", + "description", + List.of( + new CreateSnippetRequest("filename3", repeatTarget.repeat(exceedLength), 2), + new CreateSnippetRequest("filename4", "content4", 3) + ), + List.of( + new UpdateSnippetRequest(2L, "updateFilename2", "updateContent2", 1) + ), + List.of(1L), + 2L, + List.of("tag1", "tag3") + ); + + // when & then + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(updateTemplateRequest) + .when().post("/templates/1") + .then().log().all() + .statusCode(400) + .body("detail", is("파일 내용은 최대 65,535 Byte까지 입력 가능합니다.")); + } + + @ParameterizedTest + @DisplayName("템플릿 생성 실패: 템플릿 설명 길이 초과") + @CsvSource({"a, 65536", "ㄱ, 21846"}) + void updateTemplateFailWithLongContent(String repeatTarget, int exceedLength) { + // given + categoryService.create(new CreateCategoryRequest("category1")); + categoryService.create(new CreateCategoryRequest("category2")); + CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); + templateService.create(templateRequest); + + UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( + "title", + repeatTarget.repeat(exceedLength), + List.of( + new CreateSnippetRequest("filename3", "content3", 2), + new CreateSnippetRequest("filename4", "content4", 3) + ), + List.of( + new UpdateSnippetRequest(2L, "updateFilename2", "updateContent2", 1) + ), + List.of(1L), + 2L, + List.of("tag1", "tag3") + ); + + // when & then + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(updateTemplateRequest) + .when().post("/templates/1") + .then().log().all() + .statusCode(400) + .body("detail", is("템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.")); + } + // 정상 요청: 2, 3, 1 @ParameterizedTest @DisplayName("템플릿 수정 실패: 잘못된 스니펫 순서 입력") From 901e45016986f4c835cf980f89f75406133704df Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Fri, 2 Aug 2024 14:10:39 +0900 Subject: [PATCH 045/168] =?UTF-8?q?style(codezap):=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/category/controller/CategoryController.java | 5 +++-- .../template/repository/SnippetRepositoryTest.java | 2 +- .../codezap/template/service/TemplateServiceTest.java | 8 +++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index ee6179d34..9f7bb20be 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -21,7 +21,7 @@ @RestController @RequestMapping("/categories") -public class CategoryController implements SpringDocCategoryController{ +public class CategoryController implements SpringDocCategoryController { private final CategoryService categoryService; @@ -42,7 +42,8 @@ public ResponseEntity getCategories() { @PutMapping("/{id}") public ResponseEntity updateCategory(@PathVariable Long id, - @Valid @RequestBody UpdateCategoryRequest updateCategoryRequest) { + @Valid @RequestBody UpdateCategoryRequest updateCategoryRequest + ) { categoryService.update(id, updateCategoryRequest); return ResponseEntity.ok().build(); } diff --git a/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java b/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java index 5ce655dad..33dea21f2 100644 --- a/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java +++ b/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java @@ -53,7 +53,7 @@ void findOneSnippetSuccessWithTemplateAndOrdinal() { @DisplayName("스니펫 리스트 찾기 성공: 템플릿과 순서") void findSnippetsSuccessWithTemplateAndOrdinal() { Category category = categoryRepository.save(new Category("category")); - Template template = templateRepository.save(new Template("title","description", category)); + Template template = templateRepository.save(new Template("title", "description", category)); Snippet snippet1 = snippetRepository.save(new Snippet(template, "filename1", "content1", 1)); Snippet snippet2 = snippetRepository.save(new Snippet(template, "filename2", "content2", 2)); Snippet snippet3 = snippetRepository.save(new Snippet(template, "filename3", "content3", 2)); diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 1e5bd7b18..af3669a35 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -200,7 +200,13 @@ private UpdateTemplateRequest makeUpdateTemplateRequest(String title) { private Template saveTemplate(CreateTemplateRequest createTemplateRequest) { Category category = categoryRepository.save(new Category("category")); - Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title(), createTemplateRequest.description(), category)); + Template savedTemplate = templateRepository.save( + new Template( + createTemplateRequest.title(), + createTemplateRequest.description(), + category + ) + ); Snippet savedFirstSnippet = snippetRepository.save(new Snippet(savedTemplate, "filename1", "content1", 1)); snippetRepository.save(new Snippet(savedTemplate, "filename2", "content2", 2)); thumbnailSnippetRepository.save(new ThumbnailSnippet(savedTemplate, savedFirstSnippet)); From cc8db54c9fd342c0fec87290b73d5d3ab588228f Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Fri, 2 Aug 2024 15:40:30 +0900 Subject: [PATCH 046/168] =?UTF-8?q?refactor(codezap):=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A0=84=EB=B0=98=EC=A0=81=EC=9D=B8=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CategoryController.java | 3 +- .../template/service/TemplateService.java | 30 +++++++------ .../controller/CategoryControllerTest.java | 8 ++-- .../category/service/CategoryServiceTest.java | 12 +++--- .../controller/TemplateControllerTest.java | 43 ++++++------------- 5 files changed, 41 insertions(+), 55 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 9f7bb20be..8a1869652 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -41,7 +41,8 @@ public ResponseEntity getCategories() { } @PutMapping("/{id}") - public ResponseEntity updateCategory(@PathVariable Long id, + public ResponseEntity updateCategory( + @PathVariable Long id, @Valid @RequestBody UpdateCategoryRequest updateCategoryRequest ) { categoryService.update(id, updateCategoryRequest); diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index d8f2e6fd3..11534317b 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -53,22 +53,10 @@ public TemplateService(ThumbnailSnippetRepository thumbnailSnippetRepository, @Transactional public Long create(CreateTemplateRequest createTemplateRequest) { Category category = categoryRepository.fetchById(createTemplateRequest.categoryId()); - Template template = templateRepository.save( - new Template( - createTemplateRequest.title(), - createTemplateRequest.description(), - category - ) + new Template(createTemplateRequest.title(), createTemplateRequest.description(), category) ); - - List tags = createTemplateRequest.tags().stream() - .map(Tag::new) - .map(tagRepository::save) - .toList(); - - tags.forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); - + createTags(createTemplateRequest, template); createTemplateRequest.snippets() .forEach(createSnippetRequest -> createSnippet(createSnippetRequest, template)); @@ -77,6 +65,14 @@ public Long create(CreateTemplateRequest createTemplateRequest) { return template.getId(); } + private void createTags(CreateTemplateRequest createTemplateRequest, Template template) { + templateTagRepository.saveAll(createTemplateRequest.tags().stream() + .map(tag -> tagRepository.save(new Tag(tag))) + .map(tag -> new TemplateTag(template, tag)) + .toList() + ); + } + public FindAllTemplatesResponse findAll() { return FindAllTemplatesResponse.from(thumbnailSnippetRepository.findAll()); } @@ -95,7 +91,11 @@ public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) Category category = categoryRepository.fetchById(updateTemplateRequest.categoryId()); Template template = templateRepository.fetchById(templateId); template.updateTemplate(updateTemplateRequest.title(), updateTemplateRequest.description(), category); + updateSnippets(updateTemplateRequest, template); + updateTags(updateTemplateRequest, template); + } + private void updateSnippets(UpdateTemplateRequest updateTemplateRequest, Template template) { updateTemplateRequest.updateSnippets().forEach(this::updateSnippet); updateTemplateRequest.createSnippets() .forEach(createSnippetRequest -> createSnippet(createSnippetRequest, template)); @@ -107,7 +107,9 @@ public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) } updateTemplateRequest.deleteSnippetIds().forEach(snippetRepository::deleteById); + } + private void updateTags(UpdateTemplateRequest updateTemplateRequest, Template template) { templateTagRepository.deleteAllByTemplate(template); updateTemplateRequest.tags().stream() .map(Tag::new) diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index 0f79d6da4..50424a1d1 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -133,13 +133,13 @@ void updateCategoryFailWithLongName() { @Test @DisplayName("카테고리 수정 실패: 중복된 이름의 카테고리 존재") void updateCategoryFailWithDuplicatedName() { - //given + // given String duplicatedName = "duplicatedName"; categoryService.create(new CreateCategoryRequest(duplicatedName)); UpdateCategoryRequest createCategoryRequest = new UpdateCategoryRequest(duplicatedName); - //when & then + // when & then RestAssured.given().log().all() .contentType(ContentType.JSON) .body(createCategoryRequest) @@ -183,7 +183,7 @@ void updateCategoryFailWithDuplicatedName() { @Test @DisplayName("카테고리 삭제 실패: 템플릿이 존재하는 카테고리는 삭제 불가능") void updateCategoryFailWithLongName() { - //given + // given templateService.create(new CreateTemplateRequest( "title", "description", @@ -192,7 +192,7 @@ void updateCategoryFailWithLongName() { List.of("tag1", "tag2") )); - //when & then + // when & then RestAssured.given().log().all() .when().delete("/categories/" + savedCategoryId) .then().log().all() diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index 11a811216..9d7bd91af 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -80,26 +80,26 @@ void findAllCategoriesSuccess() { @Test @DisplayName("카테고리 수정 성공") void updateCategorySuccess() { - //given + // given Category savedCategory = categoryRepository.save(new Category("category1")); - //when + // when categoryService.update(savedCategory.getId(), new UpdateCategoryRequest("updateName")); - //then + // then assertThat(categoryRepository.fetchById(savedCategory.getId()).getName()).isEqualTo("updateName"); } @Test @DisplayName("카테고리 삭제 성공") void deleteCategorySuccess() { - //given + // given Category savedCategory = categoryRepository.save(new Category("category1")); - //when + // when categoryService.deleteById(savedCategory.getId()); - //then + // then assertThat(categoryRepository.findById(savedCategory.getId())).isEmpty(); } } diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index ee5abca0e..9641ff454 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -249,11 +249,7 @@ class updateTemplateTest { @DisplayName("템플릿 수정 성공") void updateTemplateSuccess() { // given - categoryService.create(new CreateCategoryRequest("category1")); - categoryService.create(new CreateCategoryRequest("category2")); - CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); - templateService.create(templateRequest); - + createTemplateAndTwoCategories(); UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( "updateTitle", "description", @@ -283,11 +279,7 @@ void updateTemplateSuccess() { void updateTemplateFailWithLongName() { // given String exceededTitle = "a".repeat(MAX_LENGTH + 1); - categoryService.create(new CreateCategoryRequest("category1")); - categoryService.create(new CreateCategoryRequest("category2")); - CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); - templateService.create(templateRequest); - + createTemplateAndTwoCategories(); UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( exceededTitle, "description", @@ -318,11 +310,7 @@ void updateTemplateFailWithLongName() { void updateTemplateFailWithLongFileName() { // given String exceededTitle = "a".repeat(MAX_LENGTH + 1); - categoryService.create(new CreateCategoryRequest("category1")); - categoryService.create(new CreateCategoryRequest("category2")); - CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); - templateService.create(templateRequest); - + createTemplateAndTwoCategories(); UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( "title", "description", @@ -353,11 +341,7 @@ void updateTemplateFailWithLongFileName() { @CsvSource({"a, 65536", "ㄱ, 21846"}) void updateTemplateFailWithLongFileContent(String repeatTarget, int exceedLength) { // given - categoryService.create(new CreateCategoryRequest("category1")); - categoryService.create(new CreateCategoryRequest("category2")); - CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); - templateService.create(templateRequest); - + createTemplateAndTwoCategories(); UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( "title", "description", @@ -388,11 +372,7 @@ void updateTemplateFailWithLongFileContent(String repeatTarget, int exceedLength @CsvSource({"a, 65536", "ㄱ, 21846"}) void updateTemplateFailWithLongContent(String repeatTarget, int exceedLength) { // given - categoryService.create(new CreateCategoryRequest("category1")); - categoryService.create(new CreateCategoryRequest("category2")); - CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); - templateService.create(templateRequest); - + createTemplateAndTwoCategories(); UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( "title", repeatTarget.repeat(exceedLength), @@ -424,11 +404,7 @@ void updateTemplateFailWithLongContent(String repeatTarget, int exceedLength) { @CsvSource({"1, 2, 1", "3, 2, 1", "0, 2, 1"}) void updateTemplateFailWithWrongSnippetOrdinal(int createOrdinal1, int createOrdinal2, int updateOrdinal) { // given - categoryService.create(new CreateCategoryRequest("category1")); - categoryService.create(new CreateCategoryRequest("category2")); - CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); - templateService.create(templateRequest); - + createTemplateAndTwoCategories(); UpdateTemplateRequest updateTemplateRequest = new UpdateTemplateRequest( "updateTitle", "description", @@ -453,6 +429,13 @@ void updateTemplateFailWithWrongSnippetOrdinal(int createOrdinal1, int createOrd .statusCode(400) .body("detail", is("스니펫 순서가 잘못되었습니다.")); } + + private void createTemplateAndTwoCategories() { + categoryService.create(new CreateCategoryRequest("category1")); + categoryService.create(new CreateCategoryRequest("category2")); + CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); + templateService.create(templateRequest); + } } @Nested From 24781a90dd4b1a560fafd73b1ac89d45773c0532 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Fri, 2 Aug 2024 17:03:50 +0900 Subject: [PATCH 047/168] =?UTF-8?q?feat(codezap):=20Valid=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=20=EC=84=A4=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/CreateCategoryRequest.java | 6 ++++-- .../dto/request/UpdateCategoryRequest.java | 6 ++++-- .../global/validation/ValidationGroups.java | 7 +++++++ .../global/validation/ValidationSequence.java | 11 +++++++++++ .../template/controller/TemplateController.java | 8 ++++---- .../dto/request/CreateSnippetRequest.java | 11 +++++++---- .../dto/request/CreateTemplateRequest.java | 13 ++++++++----- .../dto/request/UpdateSnippetRequest.java | 11 +++++++---- .../dto/request/UpdateTemplateRequest.java | 15 +++++++++------ 9 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 backend/src/main/java/codezap/global/validation/ValidationGroups.java create mode 100644 backend/src/main/java/codezap/global/validation/ValidationSequence.java diff --git a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java index 86946b8a8..5fefb1e3c 100644 --- a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java +++ b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java @@ -3,12 +3,14 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import codezap.global.validation.ValidationGroups.NotNullGroup; +import codezap.global.validation.ValidationGroups.SizeCheckGroup; import io.swagger.v3.oas.annotations.media.Schema; public record CreateCategoryRequest( @Schema(description = "카테고리 이름", example = "Spring") - @NotNull(message = "카테고리 이름이 null 입니다.") - @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.") + @NotNull(message = "카테고리 이름이 null 입니다.", groups = NotNullGroup.class) + @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String name ) { } diff --git a/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java index 4ed48bf0c..11149ba5c 100644 --- a/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java +++ b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java @@ -3,12 +3,14 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; +import codezap.global.validation.ValidationGroups.NotNullGroup; +import codezap.global.validation.ValidationGroups.SizeCheckGroup; import io.swagger.v3.oas.annotations.media.Schema; public record UpdateCategoryRequest( @Schema(description = "카테고리 이름", example = "Spring") - @NotNull(message = "카테고리 이름이 null 입니다.") - @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.") + @NotNull(message = "카테고리 이름이 null 입니다.", groups = NotNullGroup.class) + @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String name ) { } diff --git a/backend/src/main/java/codezap/global/validation/ValidationGroups.java b/backend/src/main/java/codezap/global/validation/ValidationGroups.java new file mode 100644 index 000000000..ac49b4443 --- /dev/null +++ b/backend/src/main/java/codezap/global/validation/ValidationGroups.java @@ -0,0 +1,7 @@ +package codezap.global.validation; + +public class ValidationGroups { + public interface NotNullGroup {} + public interface ByteLengthGroup {} + public interface SizeCheckGroup {} +} diff --git a/backend/src/main/java/codezap/global/validation/ValidationSequence.java b/backend/src/main/java/codezap/global/validation/ValidationSequence.java new file mode 100644 index 000000000..611530bfc --- /dev/null +++ b/backend/src/main/java/codezap/global/validation/ValidationSequence.java @@ -0,0 +1,11 @@ +package codezap.global.validation; + +import jakarta.validation.GroupSequence; + +import codezap.global.validation.ValidationGroups.ByteLengthGroup; +import codezap.global.validation.ValidationGroups.NotNullGroup; +import codezap.global.validation.ValidationGroups.SizeCheckGroup; + +@GroupSequence({NotNullGroup.class, SizeCheckGroup.class, ByteLengthGroup.class}) +public interface ValidationSequence { +} \ No newline at end of file diff --git a/backend/src/main/java/codezap/template/controller/TemplateController.java b/backend/src/main/java/codezap/template/controller/TemplateController.java index 89c93323a..dbd5ceec2 100644 --- a/backend/src/main/java/codezap/template/controller/TemplateController.java +++ b/backend/src/main/java/codezap/template/controller/TemplateController.java @@ -2,9 +2,8 @@ import java.net.URI; -import jakarta.validation.Valid; - import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -13,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import codezap.global.validation.ValidationSequence; import codezap.template.dto.request.CreateTemplateRequest; import codezap.template.dto.request.UpdateTemplateRequest; import codezap.template.dto.response.FindAllTemplatesResponse; @@ -30,7 +30,7 @@ public TemplateController(TemplateService templateService) { } @PostMapping - public ResponseEntity create(@Valid @RequestBody CreateTemplateRequest createTemplateRequest) { + public ResponseEntity create(@Validated(ValidationSequence.class) @RequestBody CreateTemplateRequest createTemplateRequest) { return ResponseEntity.created(URI.create("/templates/" + templateService.create(createTemplateRequest))) .build(); } @@ -48,7 +48,7 @@ public ResponseEntity getTemplateById(@PathVariable Lo @PostMapping("/{id}") public ResponseEntity updateTemplate( @PathVariable Long id, - @Valid @RequestBody UpdateTemplateRequest updateTemplateRequest + @Validated(ValidationSequence.class) @RequestBody UpdateTemplateRequest updateTemplateRequest ) { templateService.update(id, updateTemplateRequest); return ResponseEntity.ok().build(); diff --git a/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java index 34397eda3..d90baa5db 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java @@ -4,17 +4,20 @@ import jakarta.validation.constraints.Size; import codezap.global.validation.ByteLength; +import codezap.global.validation.ValidationGroups.ByteLengthGroup; +import codezap.global.validation.ValidationGroups.NotNullGroup; +import codezap.global.validation.ValidationGroups.SizeCheckGroup; import io.swagger.v3.oas.annotations.media.Schema; public record CreateSnippetRequest( @Schema(description = "파일 이름", example = "Main.java") - @NotNull(message = "파일 이름이 null 입니다.") - @Size(max = 255, message = "파일 이름은 최대 255자까지 입력 가능합니다.") + @NotNull(message = "파일 이름이 null 입니다.", groups = NotNullGroup.class) + @Size(max = 255, message = "파일 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String filename, @Schema(description = "소스 코드", example = "public class Main { // ...") - @NotNull(message = "파일 내용이 null 입니다.") - @ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.") + @NotNull(message = "파일 내용이 null 입니다.", groups = NotNullGroup.class) + @ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.", groups = ByteLengthGroup.class) String content, @Schema(description = "스니펫 순서", example = "1") diff --git a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java index b7e217fb0..515f79185 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java @@ -7,22 +7,25 @@ import jakarta.validation.constraints.Size; import codezap.global.validation.ByteLength; +import codezap.global.validation.ValidationGroups.ByteLengthGroup; +import codezap.global.validation.ValidationGroups.NotNullGroup; +import codezap.global.validation.ValidationGroups.SizeCheckGroup; import codezap.template.dto.request.validation.ValidatedSnippetsOrdinalRequest; import io.swagger.v3.oas.annotations.media.Schema; public record CreateTemplateRequest( @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") - @NotNull(message = "템플릿 이름이 null 입니다.") - @Size(max = 255, message = "템플릿 이름은 최대 255자까지 입력 가능합니다.") + @NotNull(message = "템플릿 이름이 null 입니다.", groups = NotNullGroup.class) + @Size(max = 255, message = "템플릿 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String title, @Schema(description = "템플릿 설명", example = "JWT를 사용하여 로그인 기능을 구현함") - @NotNull(message = "템플릿 설명이 null 입니다.") - @ByteLength(max = 65_535, message = "템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.") + @NotNull(message = "템플릿 설명이 null 입니다.", groups = NotNullGroup.class) + @ByteLength(max = 65_535, message = "템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.", groups = ByteLengthGroup.class) String description, @Schema(description = "템플릿의 스니펫 내역") - @NotNull(message = "스니펫 리스트가 null 입니다.") + @NotNull(message = "스니펫 리스트가 null 입니다.", groups = NotNullGroup.class) @Valid List snippets, diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java index bf669729e..137d6ebec 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java @@ -4,6 +4,9 @@ import jakarta.validation.constraints.Size; import codezap.global.validation.ByteLength; +import codezap.global.validation.ValidationGroups.ByteLengthGroup; +import codezap.global.validation.ValidationGroups.NotNullGroup; +import codezap.global.validation.ValidationGroups.SizeCheckGroup; import io.swagger.v3.oas.annotations.media.Schema; public record UpdateSnippetRequest( @@ -12,13 +15,13 @@ public record UpdateSnippetRequest( Long id, @Schema(description = "파일 이름", example = "Main.java") - @NotNull(message = "파일 이름이 null 입니다.") - @Size(max = 255, message = "파일 이름은 최대 255자까지 입력 가능합니다.") + @NotNull(message = "파일 이름이 null 입니다.", groups = NotNullGroup.class) + @Size(max = 255, message = "파일 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String filename, @Schema(description = "소스 코드", example = "public class Main { // ...") - @NotNull(message = "파일 내용이 null 입니다.") - @ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.") + @NotNull(message = "파일 내용이 null 입니다.", groups = NotNullGroup.class) + @ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.", groups = ByteLengthGroup.class) String content, @Schema(description = "스니펫 순서", example = "1") diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java index 52ebfbcaf..b87d3da5a 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java @@ -8,27 +8,30 @@ import jakarta.validation.constraints.Size; import codezap.global.validation.ByteLength; +import codezap.global.validation.ValidationGroups.ByteLengthGroup; +import codezap.global.validation.ValidationGroups.NotNullGroup; +import codezap.global.validation.ValidationGroups.SizeCheckGroup; import codezap.template.dto.request.validation.ValidatedSnippetsOrdinalRequest; import io.swagger.v3.oas.annotations.media.Schema; public record UpdateTemplateRequest( @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") - @NotNull(message = "템플릿 이름이 null 입니다.") - @Size(max = 255, message = "템플릿 이름은 최대 255자까지 입력 가능합니다.") + @NotNull(message = "템플릿 이름이 null 입니다.", groups = NotNullGroup.class) + @Size(max = 255, message = "템플릿 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String title, @Schema(description = "템플릿 설명", example = "JWT를 사용하여 로그인 기능을 구현함") - @NotNull(message = "템플릿 설명이 null 입니다.") - @ByteLength(max = 65_535, message = "템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.") + @NotNull(message = "템플릿 설명이 null 입니다.", groups = NotNullGroup.class) + @ByteLength(max = 65_535, message = "템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.", groups = ByteLengthGroup.class) String description, @Schema(description = "새로 추가한 스니펫 내역") - @NotNull(message = "createSnippets 리스트가 null 입니다.") + @NotNull(message = "createSnippets 리스트가 null 입니다.", groups = NotNullGroup.class) @Valid List createSnippets, @Schema(description = "삭제, 생성 스니펫을 제외한 모든 스니펫 내역") - @NotNull(message = "updateSnippets 리스트가 null 입니다.") + @NotNull(message = "updateSnippets 리스트가 null 입니다.", groups = NotNullGroup.class) @Valid List updateSnippets, From c6b77e7117642119477e7acfc5ae88a2b50a5c3f Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Fri, 2 Aug 2024 17:06:06 +0900 Subject: [PATCH 048/168] =?UTF-8?q?feat(service):=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EC=8B=9C=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/codezap/category/service/CategoryService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 48a9f5191..1b5b9cdc1 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -46,6 +46,9 @@ public void deleteById(Long id) { if (templateRepository.existsByCategoryId(id)) { throw new CodeZapException(HttpStatus.BAD_REQUEST, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다."); } + if (id == 1) { + throw new CodeZapException(HttpStatus.BAD_REQUEST, "1번 카테고리는 삭제할 수 없습니다."); + } categoryRepository.deleteById(id); } From 94679a9a83b9fef5f1ffd0d9a5a7cb3464519a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Thu, 1 Aug 2024 12:56:41 +0900 Subject: [PATCH 049/168] =?UTF-8?q?test(member):=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/FakeMemberRepository.java | 203 ++++++++++++++++++ .../member/service/MemberServiceTest.java | 59 +++++ 2 files changed, 262 insertions(+) create mode 100644 backend/src/test/java/codezap/member/repository/FakeMemberRepository.java create mode 100644 backend/src/test/java/codezap/member/service/MemberServiceTest.java diff --git a/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java b/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java new file mode 100644 index 000000000..d491cb4ea --- /dev/null +++ b/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java @@ -0,0 +1,203 @@ +package codezap.member.repository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; + +import org.springframework.data.domain.Example; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; + +import codezap.member.domain.Member; + +public class FakeMemberRepository implements MemberRepository { + + private final AtomicLong idCounter = new AtomicLong(1); + + private final List members; + + public FakeMemberRepository() { + this.members = new ArrayList<>(); + } + + @Override + public boolean existsByEmail(String email) { + return members.stream() + .anyMatch(member -> Objects.equals(member.getEmail(), email)); + } + + @Override + public boolean existsByUsername(String username) { + return members.stream() + .anyMatch(member -> Objects.equals(member.getUsername(), username)); + } + + @Override + public void flush() { + } + + @Override + public S saveAndFlush(S entity) { + return null; + } + + @Override + public List saveAllAndFlush(Iterable entities) { + return List.of(); + } + + @Override + public void deleteAllInBatch(Iterable entities) { + + } + + @Override + public void deleteAllByIdInBatch(Iterable longs) { + + } + + @Override + public void deleteAllInBatch() { + + } + + @Override + public Member getOne(Long aLong) { + return null; + } + + @Override + public Member getById(Long aLong) { + return null; + } + + @Override + public Member getReferenceById(Long aLong) { + return null; + } + + @Override + public Optional findOne(Example example) { + return Optional.empty(); + } + + @Override + public List findAll(Example example) { + return List.of(); + } + + @Override + public List findAll(Example example, Sort sort) { + return List.of(); + } + + @Override + public Page findAll(Example example, Pageable pageable) { + return null; + } + + @Override + public long count(Example example) { + return 0; + } + + @Override + public boolean exists(Example example) { + return false; + } + + @Override + public R findBy(Example example, Function, R> queryFunction) { + return null; + } + + @Override + public S save(S entity) { + var saved = new Member( + getOrGenerateId(entity), + entity.getEmail(), + entity.getPassword(), + entity.getUsername() + ); + members.removeIf(member -> Objects.equals(member.getId(), entity.getId())); + members.add(saved); + return (S) saved; + } + + @Override + public List saveAll(Iterable entities) { + return List.of(); + } + + @Override + public Optional findById(Long aLong) { + return Optional.empty(); + } + + @Override + public boolean existsById(Long aLong) { + return false; + } + + @Override + public List findAll() { + return members; + } + + @Override + public List findAllById(Iterable longs) { + return List.of(); + } + + @Override + public long count() { + return members.size(); + } + + @Override + public void deleteById(Long aLong) { + + } + + @Override + public void delete(Member entity) { + + } + + @Override + public void deleteAllById(Iterable longs) { + + } + + @Override + public void deleteAll(Iterable entities) { + + } + + @Override + public void deleteAll() { + + } + + @Override + public List findAll(Sort sort) { + return List.of(); + } + + @Override + public Page findAll(Pageable pageable) { + return null; + } + + private long getOrGenerateId(Member entity) { + if (existsById(entity.getId())) { + return entity.getId(); + } + return idCounter.getAndIncrement(); + } +} diff --git a/backend/src/test/java/codezap/member/service/MemberServiceTest.java b/backend/src/test/java/codezap/member/service/MemberServiceTest.java new file mode 100644 index 000000000..5ad664c9b --- /dev/null +++ b/backend/src/test/java/codezap/member/service/MemberServiceTest.java @@ -0,0 +1,59 @@ +package codezap.member.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import codezap.member.repository.FakeMemberRepository; +import codezap.member.dto.SignupRequest; +import codezap.global.exception.CodeZapException; +import codezap.member.domain.Member; +import codezap.member.repository.MemberRepository; + +public class MemberServiceTest { + + private final MemberRepository memberRepository = new FakeMemberRepository(); + private final MemberService sut = new MemberService(memberRepository); + + @Nested + @DisplayName("회원가입 테스트") + class SignupTest { + + @Test + @DisplayName("회원가입 성공") + void signup() { + var request = new SignupRequest("code@zap.com", "password", "chorong"); + + sut.signup(request); + + assertThat(memberRepository.findAll()).hasSize(1); + } + + @Test + @DisplayName("회원가입 실패: 이메일 중복") + void signup_fail_email_duplicate() { + var saved = new Member("code@zap.com", "pw1234", "zappy"); + memberRepository.save(saved); + var request = new SignupRequest("code@zap.com", "password", "chorong"); + + assertThatThrownBy(() -> sut.signup(request)) + .isInstanceOf(CodeZapException.class) + .hasMessageContaining("이메일이 이미 존재합니다."); + } + + @Test + @DisplayName("회원가입 실패: 사용자명 중복") + void signup_fail_username_duplicate() { + var saved = new Member("code@zap.com", "pw1234", "zappy"); + memberRepository.save(saved); + var request = new SignupRequest("chorong@zangsu.com", "password", "zappy"); + + assertThatThrownBy(() -> sut.signup(request)) + .isInstanceOf(CodeZapException.class) + .hasMessageContaining("사용자명이 이미 존재합니다."); + } + } +} From 112e0a93747b3913ed86ec674ebde84fced7715e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Thu, 1 Aug 2024 13:01:00 +0900 Subject: [PATCH 050/168] =?UTF-8?q?feat(member):=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 39 +++++++++++++++ .../java/codezap/member/domain/Member.java | 13 ++++- .../codezap/member/dto/SignupRequest.java | 8 ++++ .../member/repository/MemberRepository.java | 4 ++ .../codezap/member/service/MemberService.java | 48 +++++++++++++++++++ 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/java/codezap/member/controller/MemberController.java create mode 100644 backend/src/main/java/codezap/member/dto/SignupRequest.java create mode 100644 backend/src/main/java/codezap/member/service/MemberService.java diff --git a/backend/src/main/java/codezap/member/controller/MemberController.java b/backend/src/main/java/codezap/member/controller/MemberController.java new file mode 100644 index 000000000..9b0959c94 --- /dev/null +++ b/backend/src/main/java/codezap/member/controller/MemberController.java @@ -0,0 +1,39 @@ +package codezap.member.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import codezap.member.dto.SignupRequest; +import codezap.member.service.MemberService; +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +public class MemberController { + + private final MemberService memberService; + + @PostMapping("/signup") + @ResponseStatus(HttpStatus.CREATED) + void signup(@RequestBody SignupRequest request) { + memberService.signup(request); + } + + @GetMapping("/check-email") + ResponseEntity checkEmailDuplicate(@RequestParam String value) { + boolean isDuplicate = memberService.isEmailDuplicate(value); + return ResponseEntity.ok(isDuplicate); + } + + @GetMapping("/check-username") + ResponseEntity checkUsernameDuplicate(@RequestParam String value) { + boolean isDuplicate = memberService.isUsernameDuplicate(value); + return ResponseEntity.ok(isDuplicate); + } +} diff --git a/backend/src/main/java/codezap/member/domain/Member.java b/backend/src/main/java/codezap/member/domain/Member.java index 711092d33..dcc945f32 100644 --- a/backend/src/main/java/codezap/member/domain/Member.java +++ b/backend/src/main/java/codezap/member/domain/Member.java @@ -6,13 +6,18 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import codezap.global.auditing.BaseTimeEntity; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity -@Getter @NoArgsConstructor -public class Member { +@AllArgsConstructor +@Getter +@EqualsAndHashCode(callSuper = false) +public class Member extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -26,4 +31,8 @@ public class Member { @Column(unique = true, nullable = false) private String username; + + public Member(String email, String password, String username) { + this(null, email, password, username); + } } diff --git a/backend/src/main/java/codezap/member/dto/SignupRequest.java b/backend/src/main/java/codezap/member/dto/SignupRequest.java new file mode 100644 index 000000000..e10639066 --- /dev/null +++ b/backend/src/main/java/codezap/member/dto/SignupRequest.java @@ -0,0 +1,8 @@ +package codezap.member.dto; + +public record SignupRequest( + String email, + String password, + String username +) { +} diff --git a/backend/src/main/java/codezap/member/repository/MemberRepository.java b/backend/src/main/java/codezap/member/repository/MemberRepository.java index b109ac982..32ae33adf 100644 --- a/backend/src/main/java/codezap/member/repository/MemberRepository.java +++ b/backend/src/main/java/codezap/member/repository/MemberRepository.java @@ -5,4 +5,8 @@ import codezap.member.domain.Member; public interface MemberRepository extends JpaRepository { + + boolean existsByEmail(String email); + + boolean existsByUsername(String username); } diff --git a/backend/src/main/java/codezap/member/service/MemberService.java b/backend/src/main/java/codezap/member/service/MemberService.java new file mode 100644 index 000000000..0dadfd368 --- /dev/null +++ b/backend/src/main/java/codezap/member/service/MemberService.java @@ -0,0 +1,48 @@ +package codezap.member.service; + +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import codezap.member.dto.SignupRequest; +import codezap.global.exception.CodeZapException; +import codezap.member.domain.Member; +import codezap.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + + public void signup(SignupRequest request) { + validateSignupRequest(request); + Member member = new Member(request.email(), request.password(), request.username()); + memberRepository.save(member); + } + + private void validateSignupRequest(SignupRequest request) { + validateNotDuplicateEmail(request.email()); + validateNotDuplicateUsername(request.username()); + } + + private void validateNotDuplicateEmail(String email) { + if (isEmailDuplicate(email)) { + throw new CodeZapException(HttpStatus.CONFLICT, "이메일이 이미 존재합니다."); + } + } + + private void validateNotDuplicateUsername(String username) { + if (isUsernameDuplicate(username)) { + throw new CodeZapException(HttpStatus.CONFLICT, "사용자명이 이미 존재합니다."); + } + } + + public boolean isEmailDuplicate(String email) { + return memberRepository.existsByEmail(email); + } + + public boolean isUsernameDuplicate(String username) { + return memberRepository.existsByUsername(username); + } +} From 70efacfa4a2fcf2bd0d9644d8fa02d944fe20b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Thu, 1 Aug 2024 14:41:23 +0900 Subject: [PATCH 051/168] =?UTF-8?q?refactor(member):=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=AA=85=20=EA=B0=80=EB=8F=85=EC=84=B1=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 24 +++++++++---------- .../codezap/member/service/MemberService.java | 17 ++++++------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/backend/src/main/java/codezap/member/controller/MemberController.java b/backend/src/main/java/codezap/member/controller/MemberController.java index 9b0959c94..5760a1b7e 100644 --- a/backend/src/main/java/codezap/member/controller/MemberController.java +++ b/backend/src/main/java/codezap/member/controller/MemberController.java @@ -1,12 +1,12 @@ package codezap.member.controller; -import org.springframework.http.HttpStatus; +import java.net.URI; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import codezap.member.dto.SignupRequest; @@ -15,25 +15,25 @@ @RestController @RequiredArgsConstructor -public class MemberController { +public class MemberController implements SpringDocMemberController { private final MemberService memberService; @PostMapping("/signup") - @ResponseStatus(HttpStatus.CREATED) - void signup(@RequestBody SignupRequest request) { - memberService.signup(request); + public ResponseEntity signup(@RequestBody SignupRequest request) { + long memberId = memberService.signup(request); + return ResponseEntity.created(URI.create("/members/" + memberId)).build(); } @GetMapping("/check-email") - ResponseEntity checkEmailDuplicate(@RequestParam String value) { - boolean isDuplicate = memberService.isEmailDuplicate(value); - return ResponseEntity.ok(isDuplicate); + public ResponseEntity checkUniqueEmail(@RequestParam String email) { + boolean isUnique = memberService.isUniqueEmail(email); + return ResponseEntity.ok(isUnique); } @GetMapping("/check-username") - ResponseEntity checkUsernameDuplicate(@RequestParam String value) { - boolean isDuplicate = memberService.isUsernameDuplicate(value); - return ResponseEntity.ok(isDuplicate); + public ResponseEntity checkUniqueUsername(@RequestParam String username) { + boolean isUnique = memberService.isUniqueUsername(username); + return ResponseEntity.ok(isUnique); } } diff --git a/backend/src/main/java/codezap/member/service/MemberService.java b/backend/src/main/java/codezap/member/service/MemberService.java index 0dadfd368..f85a4683b 100644 --- a/backend/src/main/java/codezap/member/service/MemberService.java +++ b/backend/src/main/java/codezap/member/service/MemberService.java @@ -15,10 +15,11 @@ public class MemberService { private final MemberRepository memberRepository; - public void signup(SignupRequest request) { + public long signup(SignupRequest request) { validateSignupRequest(request); Member member = new Member(request.email(), request.password(), request.username()); - memberRepository.save(member); + Member saved = memberRepository.save(member); + return saved.getId(); } private void validateSignupRequest(SignupRequest request) { @@ -27,22 +28,22 @@ private void validateSignupRequest(SignupRequest request) { } private void validateNotDuplicateEmail(String email) { - if (isEmailDuplicate(email)) { + if (!isUniqueEmail(email)) { throw new CodeZapException(HttpStatus.CONFLICT, "이메일이 이미 존재합니다."); } } private void validateNotDuplicateUsername(String username) { - if (isUsernameDuplicate(username)) { + if (!isUniqueUsername(username)) { throw new CodeZapException(HttpStatus.CONFLICT, "사용자명이 이미 존재합니다."); } } - public boolean isEmailDuplicate(String email) { - return memberRepository.existsByEmail(email); + public boolean isUniqueEmail(String email) { + return !memberRepository.existsByEmail(email); } - public boolean isUsernameDuplicate(String username) { - return memberRepository.existsByUsername(username); + public boolean isUniqueUsername(String username) { + return !memberRepository.existsByUsername(username); } } From 6d3f0d92c24809afa972daddb4e0dbe2a8e04449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Thu, 1 Aug 2024 14:42:35 +0900 Subject: [PATCH 052/168] =?UTF-8?q?docs(member):=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20API=20=EB=AA=85=EC=84=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SpringDocMemberController.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 backend/src/main/java/codezap/member/controller/SpringDocMemberController.java diff --git a/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java b/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java new file mode 100644 index 000000000..187913552 --- /dev/null +++ b/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java @@ -0,0 +1,42 @@ +package codezap.member.controller; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import codezap.global.swagger.error.ApiErrorResponse; +import codezap.global.swagger.error.ErrorCase; +import codezap.member.dto.SignupRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "인증 및 인가 API", description = "회원가입 API") +public interface SpringDocMemberController { + + @Operation(summary = "회원가입") + @ApiResponse( + responseCode = "201", + description = "회원가입 성공", + headers = {@Header(name = "Location", example = "/members/1")} + ) + @ApiErrorResponse( + status = HttpStatus.CONFLICT, + instance = "/signup", + errorCases = { + @ErrorCase(description = "이메일 중복", exampleMessage = "이메일이 이미 존재합니다."), + @ErrorCase(description = "사용자명 중복", exampleMessage = "사용자명이 이미 존재합니다.") + } + ) + ResponseEntity signup(@RequestBody SignupRequest request); + + @Operation(summary = "이메일 중복 확인") + @ApiResponse(responseCode = "200", description = "사용가능한 이메일이면 true, 중복된 이메일이면 false를 반환합니다.") + ResponseEntity checkUniqueEmail(@RequestParam String email); + + @Operation(summary = "사용자명 중복 확인") + @ApiResponse(responseCode = "200", description = "사용가능한 사용자명이면 true, 중복된 사용자명이면 false를 반환합니다.") + ResponseEntity checkUniqueUsername(@RequestParam String username); +} From abd0fd18bf985da6e6f23ab91fcf4a81845430f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Fri, 2 Aug 2024 11:11:08 +0900 Subject: [PATCH 053/168] =?UTF-8?q?test(service):=20=EC=8B=A4=ED=8C=A8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codezap/member/dto/LoginRequest.java | 4 ++++ .../codezap/member/service/MemberService.java | 5 +++++ .../member/service/MemberServiceTest.java | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 backend/src/main/java/codezap/member/dto/LoginRequest.java diff --git a/backend/src/main/java/codezap/member/dto/LoginRequest.java b/backend/src/main/java/codezap/member/dto/LoginRequest.java new file mode 100644 index 000000000..13c0fca74 --- /dev/null +++ b/backend/src/main/java/codezap/member/dto/LoginRequest.java @@ -0,0 +1,4 @@ +package codezap.member.dto; + +public record LoginRequest(String email, String password) { +} diff --git a/backend/src/main/java/codezap/member/service/MemberService.java b/backend/src/main/java/codezap/member/service/MemberService.java index f85a4683b..4892c580a 100644 --- a/backend/src/main/java/codezap/member/service/MemberService.java +++ b/backend/src/main/java/codezap/member/service/MemberService.java @@ -3,6 +3,7 @@ import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import codezap.member.dto.LoginRequest; import codezap.member.dto.SignupRequest; import codezap.global.exception.CodeZapException; import codezap.member.domain.Member; @@ -22,6 +23,10 @@ public long signup(SignupRequest request) { return saved.getId(); } + public String login(LoginRequest request) { + return ""; + } + private void validateSignupRequest(SignupRequest request) { validateNotDuplicateEmail(request.email()); validateNotDuplicateUsername(request.username()); diff --git a/backend/src/test/java/codezap/member/service/MemberServiceTest.java b/backend/src/test/java/codezap/member/service/MemberServiceTest.java index 5ad664c9b..f430ab275 100644 --- a/backend/src/test/java/codezap/member/service/MemberServiceTest.java +++ b/backend/src/test/java/codezap/member/service/MemberServiceTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import codezap.member.dto.LoginRequest; import codezap.member.repository.FakeMemberRepository; import codezap.member.dto.SignupRequest; import codezap.global.exception.CodeZapException; @@ -56,4 +57,24 @@ void signup_fail_username_duplicate() { .hasMessageContaining("사용자명이 이미 존재합니다."); } } + + @Nested + @DisplayName("로그인 테스트") + class LoginTest { + + @Test + @DisplayName("로그인 성공: 액세스 토큰 반환") + void login() { + // given + var saved = new Member("code@zap.com", "pw1234", "zappy"); + memberRepository.save(saved); + var request = new LoginRequest(saved.getEmail(), saved.getPassword()); + + // when + var token = sut.login(request); + + // then + assertThat(token).isNotNull(); + } + } } From 3bb48417862bbfaf562a5f431ebaab1dedf9ae6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Fri, 2 Aug 2024 13:23:25 +0900 Subject: [PATCH 054/168] =?UTF-8?q?refactor(service):=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/codezap/member/service/MemberServiceTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/test/java/codezap/member/service/MemberServiceTest.java b/backend/src/test/java/codezap/member/service/MemberServiceTest.java index f430ab275..6c68e7f01 100644 --- a/backend/src/test/java/codezap/member/service/MemberServiceTest.java +++ b/backend/src/test/java/codezap/member/service/MemberServiceTest.java @@ -65,15 +65,12 @@ class LoginTest { @Test @DisplayName("로그인 성공: 액세스 토큰 반환") void login() { - // given var saved = new Member("code@zap.com", "pw1234", "zappy"); memberRepository.save(saved); var request = new LoginRequest(saved.getEmail(), saved.getPassword()); - // when var token = sut.login(request); - // then assertThat(token).isNotNull(); } } From 8d94d146769042bc0273d62105dfe20b3f44f188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Fri, 2 Aug 2024 13:26:37 +0900 Subject: [PATCH 055/168] =?UTF-8?q?refactor(service):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20location=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/member/controller/MemberController.java | 10 +++++----- .../member/controller/SpringDocMemberController.java | 2 +- .../java/codezap/member/service/MemberService.java | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/codezap/member/controller/MemberController.java b/backend/src/main/java/codezap/member/controller/MemberController.java index 5760a1b7e..931fd7c34 100644 --- a/backend/src/main/java/codezap/member/controller/MemberController.java +++ b/backend/src/main/java/codezap/member/controller/MemberController.java @@ -1,12 +1,12 @@ package codezap.member.controller; -import java.net.URI; - +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import codezap.member.dto.SignupRequest; @@ -20,9 +20,9 @@ public class MemberController implements SpringDocMemberController { private final MemberService memberService; @PostMapping("/signup") - public ResponseEntity signup(@RequestBody SignupRequest request) { - long memberId = memberService.signup(request); - return ResponseEntity.created(URI.create("/members/" + memberId)).build(); + @ResponseStatus(HttpStatus.CREATED) + public void signup(@RequestBody SignupRequest request) { + memberService.signup(request); } @GetMapping("/check-email") diff --git a/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java b/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java index 187913552..39779c8ac 100644 --- a/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java +++ b/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java @@ -30,7 +30,7 @@ public interface SpringDocMemberController { @ErrorCase(description = "사용자명 중복", exampleMessage = "사용자명이 이미 존재합니다.") } ) - ResponseEntity signup(@RequestBody SignupRequest request); + void signup(@RequestBody SignupRequest request); @Operation(summary = "이메일 중복 확인") @ApiResponse(responseCode = "200", description = "사용가능한 이메일이면 true, 중복된 이메일이면 false를 반환합니다.") diff --git a/backend/src/main/java/codezap/member/service/MemberService.java b/backend/src/main/java/codezap/member/service/MemberService.java index 4892c580a..61ff7591d 100644 --- a/backend/src/main/java/codezap/member/service/MemberService.java +++ b/backend/src/main/java/codezap/member/service/MemberService.java @@ -16,11 +16,10 @@ public class MemberService { private final MemberRepository memberRepository; - public long signup(SignupRequest request) { + public void signup(SignupRequest request) { validateSignupRequest(request); Member member = new Member(request.email(), request.password(), request.username()); - Member saved = memberRepository.save(member); - return saved.getId(); + memberRepository.save(member); } public String login(LoginRequest request) { From 430c233a19813b380c03d90ebd209a5b97693df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Sun, 4 Aug 2024 01:13:35 +0900 Subject: [PATCH 056/168] =?UTF-8?q?refactor(repository):=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/FakeMemberRepository.java | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java b/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java index d491cb4ea..9a69b403b 100644 --- a/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java +++ b/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java @@ -37,6 +37,38 @@ public boolean existsByUsername(String username) { .anyMatch(member -> Objects.equals(member.getUsername(), username)); } + @Override + public Optional findByEmail(String email) { + return members.stream() + .filter(member -> Objects.equals(member.getEmail(), email)) + .findFirst(); + } + + @Override + public long count() { + return members.size(); + } + + @Override + public S save(S entity) { + var saved = new Member( + getOrGenerateId(entity), + entity.getEmail(), + entity.getPassword(), + entity.getUsername() + ); + members.removeIf(member -> Objects.equals(member.getId(), entity.getId())); + members.add(saved); + return (S) saved; + } + + private long getOrGenerateId(Member entity) { + if (existsById(entity.getId())) { + return entity.getId(); + } + return idCounter.getAndIncrement(); + } + @Override public void flush() { } @@ -116,19 +148,6 @@ public R findBy(Example example, Function S save(S entity) { - var saved = new Member( - getOrGenerateId(entity), - entity.getEmail(), - entity.getPassword(), - entity.getUsername() - ); - members.removeIf(member -> Objects.equals(member.getId(), entity.getId())); - members.add(saved); - return (S) saved; - } - @Override public List saveAll(Iterable entities) { return List.of(); @@ -154,11 +173,6 @@ public List findAllById(Iterable longs) { return List.of(); } - @Override - public long count() { - return members.size(); - } - @Override public void deleteById(Long aLong) { @@ -193,11 +207,4 @@ public List findAll(Sort sort) { public Page findAll(Pageable pageable) { return null; } - - private long getOrGenerateId(Member entity) { - if (existsById(entity.getId())) { - return entity.getId(); - } - return idCounter.getAndIncrement(); - } } From 6ff7488196973215758f362ef7f6984f054267ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Sun, 4 Aug 2024 01:15:18 +0900 Subject: [PATCH 057/168] =?UTF-8?q?test(service):=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EB=B0=8F=20=EC=BF=A0=ED=82=A4=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/service/MemberServiceTest.java | 82 +++++++++++++++++-- 1 file changed, 73 insertions(+), 9 deletions(-) diff --git a/backend/src/test/java/codezap/member/service/MemberServiceTest.java b/backend/src/test/java/codezap/member/service/MemberServiceTest.java index 6c68e7f01..6d9eb92ff 100644 --- a/backend/src/test/java/codezap/member/service/MemberServiceTest.java +++ b/backend/src/test/java/codezap/member/service/MemberServiceTest.java @@ -1,17 +1,24 @@ package codezap.member.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import jakarta.servlet.http.Cookie; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; -import codezap.member.dto.LoginRequest; -import codezap.member.repository.FakeMemberRepository; -import codezap.member.dto.SignupRequest; import codezap.global.exception.CodeZapException; import codezap.member.domain.Member; +import codezap.member.dto.LoginRequest; +import codezap.member.dto.SignupRequest; +import codezap.member.repository.FakeMemberRepository; import codezap.member.repository.MemberRepository; public class MemberServiceTest { @@ -63,15 +70,72 @@ void signup_fail_username_duplicate() { class LoginTest { @Test - @DisplayName("로그인 성공: 액세스 토큰 반환") + @DisplayName("로그인 성공: Basic authentication 쿠키 값 반환") void login() { - var saved = new Member("code@zap.com", "pw1234", "zappy"); - memberRepository.save(saved); - var request = new LoginRequest(saved.getEmail(), saved.getPassword()); + // given + var member = new Member("code@zap.com", "pw1234", "zappy"); + memberRepository.save(member); + var request = new LoginRequest(member.getEmail(), member.getPassword()); + + // when + var actual = sut.login(request); + + // then + var basicAuth = member.getEmail() + ":" + member.getPassword(); + var expect = Base64.getEncoder().encodeToString(basicAuth.getBytes(StandardCharsets.UTF_8)); + assertThat(actual).isEqualTo(expect); + } - var token = sut.login(request); + @Test + @DisplayName("로그인 실패: 비밀번호 오류") + void login_fail_wrong_password() { + var member = new Member("code@zap.com", "pw1234", "zappy"); + memberRepository.save(member); + var request = new LoginRequest(member.getEmail(), "wrongpassword"); - assertThat(token).isNotNull(); + assertThatThrownBy(() -> sut.login(request)) + .isInstanceOf(CodeZapException.class) + .hasMessage("인증에 실패했습니다."); + } + } + + @Nested + @DisplayName("쿠키 인증 테스트") + class CheckLoginTest { + + @Test + @DisplayName("쿠키 인증 성공") + void checkLogin() { + var member = new Member("code@zap.com", "pw1234", "zappy"); + memberRepository.save(member); + var basicAuthCredentials = HttpHeaders.encodeBasicAuth( + member.getEmail(), + member.getPassword(), + StandardCharsets.UTF_8 + ); + var basicAuthCookie = new Cookie(HttpHeaders.AUTHORIZATION, basicAuthCredentials); + var cookies = new Cookie[]{basicAuthCookie}; + + assertThatCode(() -> sut.authorizeByCookie(cookies)) + .doesNotThrowAnyException(); + } + + @Test + @DisplayName("쿠키 인증 실패: 쿠키 값 오류") + void checkLogin_fail_wrong_cookie_value() { + var member = new Member("code@zap.com", "pw1234", "zappy"); + memberRepository.save(member); + var wrongCredentials = HttpHeaders.encodeBasicAuth( + "wrong@email.kr", + "nopassword", + StandardCharsets.UTF_8 + ); + var basicAuthCookie = new Cookie(HttpHeaders.AUTHORIZATION, wrongCredentials); + var cookies = new Cookie[]{basicAuthCookie}; + + assertThatThrownBy(() -> sut.authorizeByCookie(cookies)) + .isInstanceOf(CodeZapException.class) + .hasMessage("인증에 실패했습니다."); } } } From db9d551e7cb03e5665935cce73cc3ff12226b0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Sun, 4 Aug 2024 01:18:17 +0900 Subject: [PATCH 058/168] =?UTF-8?q?feat(member):=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 24 ++++++++ .../java/codezap/member/domain/Member.java | 6 ++ .../member/repository/MemberRepository.java | 4 ++ .../codezap/member/service/MemberService.java | 56 ++++++++++++++++--- 4 files changed, 81 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/codezap/member/controller/MemberController.java b/backend/src/main/java/codezap/member/controller/MemberController.java index 931fd7c34..76110ed1c 100644 --- a/backend/src/main/java/codezap/member/controller/MemberController.java +++ b/backend/src/main/java/codezap/member/controller/MemberController.java @@ -1,5 +1,10 @@ package codezap.member.controller; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -9,6 +14,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import codezap.member.dto.LoginRequest; import codezap.member.dto.SignupRequest; import codezap.member.service.MemberService; import lombok.RequiredArgsConstructor; @@ -36,4 +42,22 @@ public ResponseEntity checkUniqueUsername(@RequestParam String username boolean isUnique = memberService.isUniqueUsername(username); return ResponseEntity.ok(isUnique); } + + @PostMapping("/login") + @ResponseStatus(HttpStatus.OK) + public void login(@RequestBody LoginRequest request, HttpServletResponse response) { + String basicAuth = memberService.login(request); + Cookie cookie = new Cookie(HttpHeaders.AUTHORIZATION, basicAuth); + cookie.setMaxAge(-1); + cookie.setPath("/"); + cookie.setSecure(true); + cookie.setHttpOnly(true); + response.addCookie(cookie); + } + + @GetMapping("/login/check") + @ResponseStatus(HttpStatus.OK) + public void checkLogin(HttpServletRequest request) { + memberService.authorizeByCookie(request.getCookies()); + } } diff --git a/backend/src/main/java/codezap/member/domain/Member.java b/backend/src/main/java/codezap/member/domain/Member.java index dcc945f32..7aa24cd81 100644 --- a/backend/src/main/java/codezap/member/domain/Member.java +++ b/backend/src/main/java/codezap/member/domain/Member.java @@ -1,5 +1,7 @@ package codezap.member.domain; +import java.util.Objects; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -35,4 +37,8 @@ public class Member extends BaseTimeEntity { public Member(String email, String password, String username) { this(null, email, password, username); } + + public boolean matchPassword(String other) { + return Objects.equals(password, other); + } } diff --git a/backend/src/main/java/codezap/member/repository/MemberRepository.java b/backend/src/main/java/codezap/member/repository/MemberRepository.java index 32ae33adf..abf3e8485 100644 --- a/backend/src/main/java/codezap/member/repository/MemberRepository.java +++ b/backend/src/main/java/codezap/member/repository/MemberRepository.java @@ -1,5 +1,7 @@ package codezap.member.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import codezap.member.domain.Member; @@ -9,4 +11,6 @@ public interface MemberRepository extends JpaRepository { boolean existsByEmail(String email); boolean existsByUsername(String username); + + Optional findByEmail(String email); } diff --git a/backend/src/main/java/codezap/member/service/MemberService.java b/backend/src/main/java/codezap/member/service/MemberService.java index 61ff7591d..da299f84c 100644 --- a/backend/src/main/java/codezap/member/service/MemberService.java +++ b/backend/src/main/java/codezap/member/service/MemberService.java @@ -1,12 +1,20 @@ package codezap.member.service; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.Objects; + +import jakarta.servlet.http.Cookie; + +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; -import codezap.member.dto.LoginRequest; -import codezap.member.dto.SignupRequest; import codezap.global.exception.CodeZapException; import codezap.member.domain.Member; +import codezap.member.dto.LoginRequest; +import codezap.member.dto.SignupRequest; import codezap.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; @@ -17,27 +25,53 @@ public class MemberService { private final MemberRepository memberRepository; public void signup(SignupRequest request) { - validateSignupRequest(request); + assertUniqueEmail(request.email()); + assertUniqueUsername(request.username()); Member member = new Member(request.email(), request.password(), request.username()); memberRepository.save(member); } public String login(LoginRequest request) { - return ""; + authorizeByEmailAndPassword(request.email(), request.password()); + return HttpHeaders.encodeBasicAuth(request.email(), request.password(), StandardCharsets.UTF_8); } - private void validateSignupRequest(SignupRequest request) { - validateNotDuplicateEmail(request.email()); - validateNotDuplicateUsername(request.username()); + public void authorizeByCookie(Cookie[] cookies) { + String authHeaderValue = getAuthCookieValue(cookies); + String[] credentials = decodeCredentials(authHeaderValue); + String email = credentials[0]; + String password = credentials[1]; + authorizeByEmailAndPassword(email, password); } - private void validateNotDuplicateEmail(String email) { + private void authorizeByEmailAndPassword(String email, String password) { + Member member = memberRepository.findByEmail(email).orElseThrow(this::throwUnauthorized); + if (!member.matchPassword(password)) { + throwUnauthorized(); + } + } + + private String getAuthCookieValue(Cookie[] cookies) { + return Arrays.stream(cookies) + .filter(cookie -> Objects.equals(cookie.getName(), HttpHeaders.AUTHORIZATION)) + .findFirst() + .map(Cookie::getValue) + .orElseThrow(this::throwUnauthorized); + } + + private String[] decodeCredentials(String encodedCredentials) { + byte[] decodedBytes = Base64.getDecoder().decode(encodedCredentials.getBytes(StandardCharsets.UTF_8)); + String decodedString = new String(decodedBytes); + return decodedString.split(":"); + } + + private void assertUniqueEmail(String email) { if (!isUniqueEmail(email)) { throw new CodeZapException(HttpStatus.CONFLICT, "이메일이 이미 존재합니다."); } } - private void validateNotDuplicateUsername(String username) { + private void assertUniqueUsername(String username) { if (!isUniqueUsername(username)) { throw new CodeZapException(HttpStatus.CONFLICT, "사용자명이 이미 존재합니다."); } @@ -50,4 +84,8 @@ public boolean isUniqueEmail(String email) { public boolean isUniqueUsername(String username) { return !memberRepository.existsByUsername(username); } + + private CodeZapException throwUnauthorized() { + throw new CodeZapException(HttpStatus.UNAUTHORIZED, "인증에 실패했습니다."); + } } From 3cc80dffac9e1a88c01d465356c18ed84d8fbde3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Sun, 4 Aug 2024 11:21:04 +0900 Subject: [PATCH 059/168] =?UTF-8?q?refactor(repository):=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/repository/FakeMemberRepository.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java b/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java index 9a69b403b..65b692dcf 100644 --- a/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java +++ b/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java @@ -69,6 +69,11 @@ private long getOrGenerateId(Member entity) { return idCounter.getAndIncrement(); } + @Override + public boolean existsById(Long aLong) { + return false; + } + @Override public void flush() { } @@ -158,11 +163,6 @@ public Optional findById(Long aLong) { return Optional.empty(); } - @Override - public boolean existsById(Long aLong) { - return false; - } - @Override public List findAll() { return members; From 03a68c2ef37d50e23a383adfae39b1fc3b94affc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Sun, 4 Aug 2024 11:23:35 +0900 Subject: [PATCH 060/168] =?UTF-8?q?refactor(repository):=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=EC=99=80=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=97=AD=ED=95=A0=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 23 +++++++++++-------- .../java/codezap/member/dto/MemberDto.java | 14 +++++++++++ .../codezap/member/service/MemberService.java | 13 ++++++----- .../member/service/MemberServiceTest.java | 11 ++++----- 4 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 backend/src/main/java/codezap/member/dto/MemberDto.java diff --git a/backend/src/main/java/codezap/member/controller/MemberController.java b/backend/src/main/java/codezap/member/controller/MemberController.java index 76110ed1c..ba139f360 100644 --- a/backend/src/main/java/codezap/member/controller/MemberController.java +++ b/backend/src/main/java/codezap/member/controller/MemberController.java @@ -1,11 +1,13 @@ package codezap.member.controller; -import jakarta.servlet.http.Cookie; +import java.nio.charset.StandardCharsets; + import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -15,6 +17,7 @@ import org.springframework.web.bind.annotation.RestController; import codezap.member.dto.LoginRequest; +import codezap.member.dto.MemberDto; import codezap.member.dto.SignupRequest; import codezap.member.service.MemberService; import lombok.RequiredArgsConstructor; @@ -46,18 +49,20 @@ public ResponseEntity checkUniqueUsername(@RequestParam String username @PostMapping("/login") @ResponseStatus(HttpStatus.OK) public void login(@RequestBody LoginRequest request, HttpServletResponse response) { - String basicAuth = memberService.login(request); - Cookie cookie = new Cookie(HttpHeaders.AUTHORIZATION, basicAuth); - cookie.setMaxAge(-1); - cookie.setPath("/"); - cookie.setSecure(true); - cookie.setHttpOnly(true); - response.addCookie(cookie); + MemberDto member = memberService.login(request); + String basicAuth = HttpHeaders.encodeBasicAuth(member.email(), member.password(), StandardCharsets.UTF_8); + ResponseCookie cookie = ResponseCookie.from(HttpHeaders.AUTHORIZATION, basicAuth) + .maxAge(-1) + .path("/") + .secure(true) + .httpOnly(true) + .build(); + response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString()); } @GetMapping("/login/check") @ResponseStatus(HttpStatus.OK) public void checkLogin(HttpServletRequest request) { - memberService.authorizeByCookie(request.getCookies()); + memberService.login(request.getCookies()); } } diff --git a/backend/src/main/java/codezap/member/dto/MemberDto.java b/backend/src/main/java/codezap/member/dto/MemberDto.java new file mode 100644 index 000000000..9b30ea229 --- /dev/null +++ b/backend/src/main/java/codezap/member/dto/MemberDto.java @@ -0,0 +1,14 @@ +package codezap.member.dto; + +import codezap.member.domain.Member; + +public record MemberDto( + Long id, + String email, + String password, + String username +) { + public static MemberDto from(Member member) { + return new MemberDto(member.getId(), member.getEmail(), member.getPassword(), member.getUsername()); + } +} diff --git a/backend/src/main/java/codezap/member/service/MemberService.java b/backend/src/main/java/codezap/member/service/MemberService.java index da299f84c..45fbe08c2 100644 --- a/backend/src/main/java/codezap/member/service/MemberService.java +++ b/backend/src/main/java/codezap/member/service/MemberService.java @@ -14,6 +14,7 @@ import codezap.global.exception.CodeZapException; import codezap.member.domain.Member; import codezap.member.dto.LoginRequest; +import codezap.member.dto.MemberDto; import codezap.member.dto.SignupRequest; import codezap.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; @@ -31,24 +32,24 @@ public void signup(SignupRequest request) { memberRepository.save(member); } - public String login(LoginRequest request) { - authorizeByEmailAndPassword(request.email(), request.password()); - return HttpHeaders.encodeBasicAuth(request.email(), request.password(), StandardCharsets.UTF_8); + public MemberDto login(LoginRequest request) { + return login(request.email(), request.password()); } - public void authorizeByCookie(Cookie[] cookies) { + public MemberDto login(Cookie[] cookies) { String authHeaderValue = getAuthCookieValue(cookies); String[] credentials = decodeCredentials(authHeaderValue); String email = credentials[0]; String password = credentials[1]; - authorizeByEmailAndPassword(email, password); + return login(email, password); } - private void authorizeByEmailAndPassword(String email, String password) { + private MemberDto login(String email, String password) { Member member = memberRepository.findByEmail(email).orElseThrow(this::throwUnauthorized); if (!member.matchPassword(password)) { throwUnauthorized(); } + return MemberDto.from(member); } private String getAuthCookieValue(Cookie[] cookies) { diff --git a/backend/src/test/java/codezap/member/service/MemberServiceTest.java b/backend/src/test/java/codezap/member/service/MemberServiceTest.java index 6d9eb92ff..0fe99de29 100644 --- a/backend/src/test/java/codezap/member/service/MemberServiceTest.java +++ b/backend/src/test/java/codezap/member/service/MemberServiceTest.java @@ -5,7 +5,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.nio.charset.StandardCharsets; -import java.util.Base64; import jakarta.servlet.http.Cookie; @@ -17,6 +16,7 @@ import codezap.global.exception.CodeZapException; import codezap.member.domain.Member; import codezap.member.dto.LoginRequest; +import codezap.member.dto.MemberDto; import codezap.member.dto.SignupRequest; import codezap.member.repository.FakeMemberRepository; import codezap.member.repository.MemberRepository; @@ -73,7 +73,7 @@ class LoginTest { @DisplayName("로그인 성공: Basic authentication 쿠키 값 반환") void login() { // given - var member = new Member("code@zap.com", "pw1234", "zappy"); + var member = new Member(1L, "code@zap.com", "pw1234", "zappy"); memberRepository.save(member); var request = new LoginRequest(member.getEmail(), member.getPassword()); @@ -81,8 +81,7 @@ void login() { var actual = sut.login(request); // then - var basicAuth = member.getEmail() + ":" + member.getPassword(); - var expect = Base64.getEncoder().encodeToString(basicAuth.getBytes(StandardCharsets.UTF_8)); + var expect = MemberDto.from(member); assertThat(actual).isEqualTo(expect); } @@ -116,7 +115,7 @@ void checkLogin() { var basicAuthCookie = new Cookie(HttpHeaders.AUTHORIZATION, basicAuthCredentials); var cookies = new Cookie[]{basicAuthCookie}; - assertThatCode(() -> sut.authorizeByCookie(cookies)) + assertThatCode(() -> sut.login(cookies)) .doesNotThrowAnyException(); } @@ -133,7 +132,7 @@ void checkLogin_fail_wrong_cookie_value() { var basicAuthCookie = new Cookie(HttpHeaders.AUTHORIZATION, wrongCredentials); var cookies = new Cookie[]{basicAuthCookie}; - assertThatThrownBy(() -> sut.authorizeByCookie(cookies)) + assertThatThrownBy(() -> sut.login(cookies)) .isInstanceOf(CodeZapException.class) .hasMessage("인증에 실패했습니다."); } From 090bdce2ac3b54b229db8c259dbc21887a1864db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Sun, 4 Aug 2024 11:26:40 +0900 Subject: [PATCH 061/168] =?UTF-8?q?feat(configuration):=20=EC=BF=A0?= =?UTF-8?q?=ED=82=A4=20=EC=9D=B8=EC=A6=9D=20argument=20resolver=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/AuthArgumentResolver.java | 34 +++++++++++++++++++ .../configuration/AuthWebConfiguration.java | 22 ++++++++++++ .../configuration/BasicAuthentication.java | 4 +++ 3 files changed, 60 insertions(+) create mode 100644 backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java create mode 100644 backend/src/main/java/codezap/member/configuration/AuthWebConfiguration.java create mode 100644 backend/src/main/java/codezap/member/configuration/BasicAuthentication.java diff --git a/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java b/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java new file mode 100644 index 000000000..dc28cb299 --- /dev/null +++ b/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java @@ -0,0 +1,34 @@ +package codezap.member.configuration; + +import jakarta.servlet.http.HttpServletRequest; + +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import codezap.member.service.MemberService; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class AuthArgumentResolver implements HandlerMethodArgumentResolver { + + private final MemberService memberService; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(BasicAuthentication.class); + } + + @Override + public Object resolveArgument( + MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory + ) { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + return memberService.login(request.getCookies()); + } +} diff --git a/backend/src/main/java/codezap/member/configuration/AuthWebConfiguration.java b/backend/src/main/java/codezap/member/configuration/AuthWebConfiguration.java new file mode 100644 index 000000000..c009b306f --- /dev/null +++ b/backend/src/main/java/codezap/member/configuration/AuthWebConfiguration.java @@ -0,0 +1,22 @@ +package codezap.member.configuration; + +import java.util.List; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import codezap.member.service.MemberService; +import lombok.RequiredArgsConstructor; + +@Configuration +@RequiredArgsConstructor +public class AuthWebConfiguration implements WebMvcConfigurer { + + private final MemberService memberService; + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new AuthArgumentResolver(memberService)); + } +} diff --git a/backend/src/main/java/codezap/member/configuration/BasicAuthentication.java b/backend/src/main/java/codezap/member/configuration/BasicAuthentication.java new file mode 100644 index 000000000..ffb743174 --- /dev/null +++ b/backend/src/main/java/codezap/member/configuration/BasicAuthentication.java @@ -0,0 +1,4 @@ +package codezap.member.configuration; + +public @interface BasicAuthentication { +} From 9dc6fae0fd3db4591b1d08a146f94369f09a70ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Sun, 4 Aug 2024 15:01:22 +0900 Subject: [PATCH 062/168] =?UTF-8?q?refactor(service):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B0=80=EB=8F=85=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codezap/member/service/MemberServiceTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/test/java/codezap/member/service/MemberServiceTest.java b/backend/src/test/java/codezap/member/service/MemberServiceTest.java index 0fe99de29..557764ad0 100644 --- a/backend/src/test/java/codezap/member/service/MemberServiceTest.java +++ b/backend/src/test/java/codezap/member/service/MemberServiceTest.java @@ -15,11 +15,11 @@ import codezap.global.exception.CodeZapException; import codezap.member.domain.Member; +import codezap.member.repository.MemberRepository; import codezap.member.dto.LoginRequest; import codezap.member.dto.MemberDto; import codezap.member.dto.SignupRequest; import codezap.member.repository.FakeMemberRepository; -import codezap.member.repository.MemberRepository; public class MemberServiceTest { @@ -35,9 +35,10 @@ class SignupTest { void signup() { var request = new SignupRequest("code@zap.com", "password", "chorong"); - sut.signup(request); + var actual = sut.signup(request); - assertThat(memberRepository.findAll()).hasSize(1); + var expect = new Member(1L, request.email(), request.password(), request.username()); + assertThat(actual).isEqualTo(expect); } @Test @@ -70,7 +71,7 @@ void signup_fail_username_duplicate() { class LoginTest { @Test - @DisplayName("로그인 성공: Basic authentication 쿠키 값 반환") + @DisplayName("로그인 성공") void login() { // given var member = new Member(1L, "code@zap.com", "pw1234", "zappy"); From 2ee0a621fcd1f1edd56a15b0defe71bd8bcf3082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Sun, 4 Aug 2024 15:02:04 +0900 Subject: [PATCH 063/168] =?UTF-8?q?refactor(service):=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EB=A9=94=EC=84=9C=EB=93=9C=EA=B0=80=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/codezap/member/service/MemberService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/codezap/member/service/MemberService.java b/backend/src/main/java/codezap/member/service/MemberService.java index 45fbe08c2..e862e5c04 100644 --- a/backend/src/main/java/codezap/member/service/MemberService.java +++ b/backend/src/main/java/codezap/member/service/MemberService.java @@ -13,10 +13,10 @@ import codezap.global.exception.CodeZapException; import codezap.member.domain.Member; +import codezap.member.repository.MemberRepository; import codezap.member.dto.LoginRequest; import codezap.member.dto.MemberDto; import codezap.member.dto.SignupRequest; -import codezap.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; @Service @@ -25,11 +25,11 @@ public class MemberService { private final MemberRepository memberRepository; - public void signup(SignupRequest request) { + public Member signup(SignupRequest request) { assertUniqueEmail(request.email()); assertUniqueUsername(request.username()); Member member = new Member(request.email(), request.password(), request.username()); - memberRepository.save(member); + return memberRepository.save(member); } public MemberDto login(LoginRequest request) { From a2fc045616bb48393c3dbb4bfb8d1eba7104ade4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Sun, 4 Aug 2024 15:02:26 +0900 Subject: [PATCH 064/168] =?UTF-8?q?refactor(service):=20repository=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/MemberJpaRepository.java | 17 ++ .../member/repository/MemberRepository.java | 6 +- .../repository/FakeMemberRepository.java | 156 +----------------- 3 files changed, 25 insertions(+), 154 deletions(-) create mode 100644 backend/src/main/java/codezap/member/repository/MemberJpaRepository.java diff --git a/backend/src/main/java/codezap/member/repository/MemberJpaRepository.java b/backend/src/main/java/codezap/member/repository/MemberJpaRepository.java new file mode 100644 index 000000000..6eb5a07b5 --- /dev/null +++ b/backend/src/main/java/codezap/member/repository/MemberJpaRepository.java @@ -0,0 +1,17 @@ +package codezap.member.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import codezap.member.domain.Member; + +@SuppressWarnings("unused") +public interface MemberJpaRepository extends MemberRepository, JpaRepository { + + boolean existsByEmail(String email); + + boolean existsByUsername(String username); + + Optional findByEmail(String email); +} diff --git a/backend/src/main/java/codezap/member/repository/MemberRepository.java b/backend/src/main/java/codezap/member/repository/MemberRepository.java index abf3e8485..b5189d3a1 100644 --- a/backend/src/main/java/codezap/member/repository/MemberRepository.java +++ b/backend/src/main/java/codezap/member/repository/MemberRepository.java @@ -2,15 +2,15 @@ import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; - import codezap.member.domain.Member; -public interface MemberRepository extends JpaRepository { +public interface MemberRepository { boolean existsByEmail(String email); boolean existsByUsername(String username); Optional findByEmail(String email); + + Member save(Member member); } diff --git a/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java b/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java index 65b692dcf..a6c3728c4 100644 --- a/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java +++ b/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java @@ -5,13 +5,6 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Function; - -import org.springframework.data.domain.Example; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; import codezap.member.domain.Member; @@ -45,12 +38,7 @@ public Optional findByEmail(String email) { } @Override - public long count() { - return members.size(); - } - - @Override - public S save(S entity) { + public Member save(Member entity) { var saved = new Member( getOrGenerateId(entity), entity.getEmail(), @@ -59,7 +47,7 @@ public S save(S entity) { ); members.removeIf(member -> Objects.equals(member.getId(), entity.getId())); members.add(saved); - return (S) saved; + return saved; } private long getOrGenerateId(Member entity) { @@ -69,142 +57,8 @@ private long getOrGenerateId(Member entity) { return idCounter.getAndIncrement(); } - @Override - public boolean existsById(Long aLong) { - return false; - } - - @Override - public void flush() { - } - - @Override - public S saveAndFlush(S entity) { - return null; - } - - @Override - public List saveAllAndFlush(Iterable entities) { - return List.of(); - } - - @Override - public void deleteAllInBatch(Iterable entities) { - - } - - @Override - public void deleteAllByIdInBatch(Iterable longs) { - - } - - @Override - public void deleteAllInBatch() { - - } - - @Override - public Member getOne(Long aLong) { - return null; - } - - @Override - public Member getById(Long aLong) { - return null; - } - - @Override - public Member getReferenceById(Long aLong) { - return null; - } - - @Override - public Optional findOne(Example example) { - return Optional.empty(); - } - - @Override - public List findAll(Example example) { - return List.of(); - } - - @Override - public List findAll(Example example, Sort sort) { - return List.of(); - } - - @Override - public Page findAll(Example example, Pageable pageable) { - return null; - } - - @Override - public long count(Example example) { - return 0; - } - - @Override - public boolean exists(Example example) { - return false; - } - - @Override - public R findBy(Example example, Function, R> queryFunction) { - return null; - } - - @Override - public List saveAll(Iterable entities) { - return List.of(); - } - - @Override - public Optional findById(Long aLong) { - return Optional.empty(); - } - - @Override - public List findAll() { - return members; - } - - @Override - public List findAllById(Iterable longs) { - return List.of(); - } - - @Override - public void deleteById(Long aLong) { - - } - - @Override - public void delete(Member entity) { - - } - - @Override - public void deleteAllById(Iterable longs) { - - } - - @Override - public void deleteAll(Iterable entities) { - - } - - @Override - public void deleteAll() { - - } - - @Override - public List findAll(Sort sort) { - return List.of(); - } - - @Override - public Page findAll(Pageable pageable) { - return null; + private boolean existsById(Long id) { + return members.stream() + .anyMatch(member -> Objects.equals(member.getId(), id)); } } From e18434b3b675a7e0a67569ff752961d743ea2acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Sun, 4 Aug 2024 15:26:14 +0900 Subject: [PATCH 065/168] =?UTF-8?q?refactor(service):=20=EC=BF=A0=ED=82=A4?= =?UTF-8?q?=20=EC=9D=B8=EC=A6=9D=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuration/AuthArgumentResolver.java | 2 +- .../member/controller/MemberController.java | 2 +- .../codezap/member/service/AuthService.java | 59 +++++++++++++++++++ .../codezap/member/service/MemberService.java | 45 ++------------ .../member/service/MemberServiceTest.java | 7 ++- 5 files changed, 70 insertions(+), 45 deletions(-) create mode 100644 backend/src/main/java/codezap/member/service/AuthService.java diff --git a/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java b/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java index dc28cb299..9e745a957 100644 --- a/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java +++ b/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java @@ -29,6 +29,6 @@ public Object resolveArgument( WebDataBinderFactory binderFactory ) { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); - return memberService.login(request.getCookies()); + return memberService.checkLogin(request.getCookies()); } } diff --git a/backend/src/main/java/codezap/member/controller/MemberController.java b/backend/src/main/java/codezap/member/controller/MemberController.java index ba139f360..ff90e619c 100644 --- a/backend/src/main/java/codezap/member/controller/MemberController.java +++ b/backend/src/main/java/codezap/member/controller/MemberController.java @@ -63,6 +63,6 @@ public void login(@RequestBody LoginRequest request, HttpServletResponse respons @GetMapping("/login/check") @ResponseStatus(HttpStatus.OK) public void checkLogin(HttpServletRequest request) { - memberService.login(request.getCookies()); + memberService.checkLogin(request.getCookies()); } } diff --git a/backend/src/main/java/codezap/member/service/AuthService.java b/backend/src/main/java/codezap/member/service/AuthService.java new file mode 100644 index 000000000..3f7860935 --- /dev/null +++ b/backend/src/main/java/codezap/member/service/AuthService.java @@ -0,0 +1,59 @@ +package codezap.member.service; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.Objects; + +import jakarta.servlet.http.Cookie; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import codezap.global.exception.CodeZapException; +import codezap.member.domain.Member; +import codezap.member.dto.MemberDto; +import codezap.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final MemberRepository memberRepository; + + public MemberDto authorizeByEmailAndPassword(String email, String password) { + Member member = memberRepository.findByEmail(email).orElseThrow(this::throwUnauthorized); + if (!member.matchPassword(password)) { + throwUnauthorized(); + } + return MemberDto.from(member); + } + + public MemberDto authorizeByCookie(Cookie[] cookies) { + String authHeaderValue = getAuthCookieValue(cookies); + String[] credentials = decodeCredentials(authHeaderValue); + String email = credentials[0]; + String password = credentials[1]; + return authorizeByEmailAndPassword(email, password); + } + + private String getAuthCookieValue(Cookie[] cookies) { + return Arrays.stream(cookies) + .filter(cookie -> Objects.equals(cookie.getName(), HttpHeaders.AUTHORIZATION)) + .findFirst() + .map(Cookie::getValue) + .orElseThrow(this::throwUnauthorized); + } + + private String[] decodeCredentials(String encodedCredentials) { + byte[] decodedBytes = Base64.getDecoder().decode(encodedCredentials.getBytes(StandardCharsets.UTF_8)); + String decodedString = new String(decodedBytes); + return decodedString.split(":"); + } + + private CodeZapException throwUnauthorized() { + throw new CodeZapException(HttpStatus.UNAUTHORIZED, "인증에 실패했습니다."); + } +} diff --git a/backend/src/main/java/codezap/member/service/MemberService.java b/backend/src/main/java/codezap/member/service/MemberService.java index e862e5c04..b2e927e2e 100644 --- a/backend/src/main/java/codezap/member/service/MemberService.java +++ b/backend/src/main/java/codezap/member/service/MemberService.java @@ -1,22 +1,16 @@ package codezap.member.service; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Base64; -import java.util.Objects; - import jakarta.servlet.http.Cookie; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import codezap.global.exception.CodeZapException; import codezap.member.domain.Member; -import codezap.member.repository.MemberRepository; import codezap.member.dto.LoginRequest; import codezap.member.dto.MemberDto; import codezap.member.dto.SignupRequest; +import codezap.member.repository.MemberRepository; import lombok.RequiredArgsConstructor; @Service @@ -24,6 +18,7 @@ public class MemberService { private final MemberRepository memberRepository; + private final AuthService authService; public Member signup(SignupRequest request) { assertUniqueEmail(request.email()); @@ -33,37 +28,11 @@ public Member signup(SignupRequest request) { } public MemberDto login(LoginRequest request) { - return login(request.email(), request.password()); - } - - public MemberDto login(Cookie[] cookies) { - String authHeaderValue = getAuthCookieValue(cookies); - String[] credentials = decodeCredentials(authHeaderValue); - String email = credentials[0]; - String password = credentials[1]; - return login(email, password); - } - - private MemberDto login(String email, String password) { - Member member = memberRepository.findByEmail(email).orElseThrow(this::throwUnauthorized); - if (!member.matchPassword(password)) { - throwUnauthorized(); - } - return MemberDto.from(member); - } - - private String getAuthCookieValue(Cookie[] cookies) { - return Arrays.stream(cookies) - .filter(cookie -> Objects.equals(cookie.getName(), HttpHeaders.AUTHORIZATION)) - .findFirst() - .map(Cookie::getValue) - .orElseThrow(this::throwUnauthorized); + return authService.authorizeByEmailAndPassword(request.email(), request.password()); } - private String[] decodeCredentials(String encodedCredentials) { - byte[] decodedBytes = Base64.getDecoder().decode(encodedCredentials.getBytes(StandardCharsets.UTF_8)); - String decodedString = new String(decodedBytes); - return decodedString.split(":"); + public MemberDto checkLogin(Cookie[] cookies) { + return authService.authorizeByCookie(cookies); } private void assertUniqueEmail(String email) { @@ -85,8 +54,4 @@ public boolean isUniqueEmail(String email) { public boolean isUniqueUsername(String username) { return !memberRepository.existsByUsername(username); } - - private CodeZapException throwUnauthorized() { - throw new CodeZapException(HttpStatus.UNAUTHORIZED, "인증에 실패했습니다."); - } } diff --git a/backend/src/test/java/codezap/member/service/MemberServiceTest.java b/backend/src/test/java/codezap/member/service/MemberServiceTest.java index 557764ad0..14b1903bc 100644 --- a/backend/src/test/java/codezap/member/service/MemberServiceTest.java +++ b/backend/src/test/java/codezap/member/service/MemberServiceTest.java @@ -24,7 +24,8 @@ public class MemberServiceTest { private final MemberRepository memberRepository = new FakeMemberRepository(); - private final MemberService sut = new MemberService(memberRepository); + private final AuthService authService = new AuthService(memberRepository); + private final MemberService sut = new MemberService(memberRepository, authService); @Nested @DisplayName("회원가입 테스트") @@ -116,7 +117,7 @@ void checkLogin() { var basicAuthCookie = new Cookie(HttpHeaders.AUTHORIZATION, basicAuthCredentials); var cookies = new Cookie[]{basicAuthCookie}; - assertThatCode(() -> sut.login(cookies)) + assertThatCode(() -> sut.checkLogin(cookies)) .doesNotThrowAnyException(); } @@ -133,7 +134,7 @@ void checkLogin_fail_wrong_cookie_value() { var basicAuthCookie = new Cookie(HttpHeaders.AUTHORIZATION, wrongCredentials); var cookies = new Cookie[]{basicAuthCookie}; - assertThatThrownBy(() -> sut.login(cookies)) + assertThatThrownBy(() -> sut.checkLogin(cookies)) .isInstanceOf(CodeZapException.class) .hasMessage("인증에 실패했습니다."); } From cfec85651e26345a65302f37fcc5f8805f767fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Mon, 5 Aug 2024 14:47:00 +0900 Subject: [PATCH 066/168] =?UTF-8?q?docs(member):=20API=20=EB=AA=85?= =?UTF-8?q?=EC=84=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 5 +- .../controller/SpringDocMemberController.java | 262 +++++++++++++++++- .../java/codezap/member/dto/LoginRequest.java | 25 +- .../codezap/member/dto/SignupRequest.java | 19 ++ 4 files changed, 297 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/codezap/member/controller/MemberController.java b/backend/src/main/java/codezap/member/controller/MemberController.java index ff90e619c..8496e7562 100644 --- a/backend/src/main/java/codezap/member/controller/MemberController.java +++ b/backend/src/main/java/codezap/member/controller/MemberController.java @@ -4,6 +4,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -30,7 +31,7 @@ public class MemberController implements SpringDocMemberController { @PostMapping("/signup") @ResponseStatus(HttpStatus.CREATED) - public void signup(@RequestBody SignupRequest request) { + public void signup(@Valid @RequestBody SignupRequest request) { memberService.signup(request); } @@ -48,7 +49,7 @@ public ResponseEntity checkUniqueUsername(@RequestParam String username @PostMapping("/login") @ResponseStatus(HttpStatus.OK) - public void login(@RequestBody LoginRequest request, HttpServletResponse response) { + public void login(@Valid @RequestBody LoginRequest request, HttpServletResponse response) { MemberDto member = memberService.login(request); String basicAuth = HttpHeaders.encodeBasicAuth(member.email(), member.password(), StandardCharsets.UTF_8); ResponseCookie cookie = ResponseCookie.from(HttpHeaders.AUTHORIZATION, basicAuth) diff --git a/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java b/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java index 39779c8ac..bde2fe8a5 100644 --- a/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java +++ b/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java @@ -1,5 +1,8 @@ package codezap.member.controller; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; @@ -7,9 +10,14 @@ import codezap.global.swagger.error.ApiErrorResponse; import codezap.global.swagger.error.ErrorCase; +import codezap.global.swagger.error.ProblemDetailSchema; +import codezap.member.dto.LoginRequest; import codezap.member.dto.SignupRequest; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.headers.Header; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; @@ -19,24 +27,256 @@ public interface SpringDocMemberController { @Operation(summary = "회원가입") @ApiResponse( responseCode = "201", - description = "회원가입 성공", - headers = {@Header(name = "Location", example = "/members/1")} + description = "회원가입 성공" ) - @ApiErrorResponse( - status = HttpStatus.CONFLICT, - instance = "/signup", - errorCases = { - @ErrorCase(description = "이메일 중복", exampleMessage = "이메일이 이미 존재합니다."), - @ErrorCase(description = "사용자명 중복", exampleMessage = "사용자명이 이미 존재합니다.") - } + @ApiResponse( + responseCode = "400", + description = "요청 형식 오류", + content = @Content( + schema = @Schema(implementation = ProblemDetailSchema.class), + examples = { + @ExampleObject(name = "이메일 입력 없음", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "이메일이 입력되지 않았습니다.", + "instance": "/signup" + } + """ + ), + @ExampleObject(name = "이메일 형식 오류", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "이메일 형식이 아닙니다.", + "instance": "/signup" + } + """ + ), + @ExampleObject(name = "이메일 글자수 오류", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "이메일은 255자 이하로 입력해주세요.", + "instance": "/signup" + } + """ + ), + @ExampleObject(name = "비밀번호 입력 없음", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "비밀번호가 입력되지 않았습니다.", + "instance": "/signup" + } + """ + ), + @ExampleObject(name = "비밀번호 형식 오류", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "영어와 숫자를 포함해야합니다.", + "instance": "/signup" + } + """ + ), + @ExampleObject(name = "비밀번호 글자수 오류", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "비밀번호는 8~16자 사이로 입력해주세요.", + "instance": "/signup" + } + """ + ), + @ExampleObject(name = "사용자명 입력 없음", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "사용자명이 입력되지 않았습니다.", + "instance": "/signup" + } + """ + ), + @ExampleObject(name = "사용자명 글자수 오류", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "사용자명이 입력되지 않았습니다.", + "instance": "/signup" + } + """ + ), + } + ) + ) + @ApiResponse( + responseCode = "409", + description = "이메일 또는 사용자명 중복", + content = @Content( + schema = @Schema(implementation = ProblemDetailSchema.class), + examples = { + @ExampleObject(name = "이메일 중복", value = """ + { + "type": "about:blank", + "title": "CONFLICT", + "status": 409, + "detail": "이메일이 이미 존재합니다.", + "instance": "/signup" + } + """ + ), + @ExampleObject(name = "사용자명 중복", value = """ + { + "type": "about:blank", + "title": "CONFLICT", + "status": 409, + "detail": "사용자명이 이미 존재합니다.", + "instance": "/signup" + } + """ + ) + } + ) ) void signup(@RequestBody SignupRequest request); @Operation(summary = "이메일 중복 확인") - @ApiResponse(responseCode = "200", description = "사용가능한 이메일이면 true, 중복된 이메일이면 false를 반환합니다.") + @ApiResponse(responseCode = "200", description = "사용가능한 이메일이면 true, 중복된 이메일이면 false를 반환") ResponseEntity checkUniqueEmail(@RequestParam String email); @Operation(summary = "사용자명 중복 확인") - @ApiResponse(responseCode = "200", description = "사용가능한 사용자명이면 true, 중복된 사용자명이면 false를 반환합니다.") + @ApiResponse(responseCode = "200", description = "사용가능한 사용자명이면 true, 중복된 사용자명이면 false를 반환") ResponseEntity checkUniqueUsername(@RequestParam String username); + + @Operation(summary = "이메일 로그인") + @ApiResponse( + responseCode = "200", + description = "로그인 성공", + headers = {@Header(name = "Cookie", description = "base64(${email}:${password})")} + ) + @ApiResponse( + responseCode = "400", + description = "요청 형식 오류", + content = @Content( + schema = @Schema(implementation = ProblemDetailSchema.class), + examples = { + @ExampleObject(name = "이메일 입력 없음", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "이메일이 입력되지 않았습니다.", + "instance": "/login" + } + """ + ), + @ExampleObject(name = "이메일 형식 오류", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "이메일 형식이 아닙니다.", + "instance": "/login" + } + """ + ), + @ExampleObject(name = "이메일 글자수 오류", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "이메일은 255자 이하로 입력해주세요.", + "instance": "/login" + } + """ + ), + @ExampleObject(name = "비밀번호 입력 없음", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "비밀번호가 입력되지 않았습니다.", + "instance": "/login" + } + """ + ), + @ExampleObject(name = "비밀번호 형식 오류", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "영어와 숫자를 포함해야합니다.", + "instance": "/login" + } + """ + ), + @ExampleObject(name = "비밀번호 글자수 오류", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 400, + "detail": "비밀번호는 8~16자 사이로 입력해주세요.", + "instance": "/login" + } + """ + ) + } + ) + ) + @ApiResponse( + responseCode = "401", + description = "요청 형식 오류", + content = @Content( + schema = @Schema(implementation = ProblemDetailSchema.class), + examples = { + @ExampleObject(name = "이메일 불일치", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 401, + "detail": "이메일이 입력되지 않았습니다.", + "instance": "/login" + } + """ + ), + @ExampleObject(name = "비밀번호 불일치", value = """ + { + "type": "about:blank", + "title": "BAD_REQUEST", + "status": 401, + "detail": "비밀번호가 입력되지 않았습니다.", + "instance": "/login" + } + """ + ), + } + ) + ) + @ApiErrorResponse( + status = HttpStatus.UNAUTHORIZED, + instance = "/login", + errorCases = {@ErrorCase(description = "이메일 또는 비밀번호 불일치", exampleMessage = "인증에 실패했습니다.")} + ) + void login(LoginRequest request, HttpServletResponse response); + + @Operation(summary = "이메일 로그인 후 쿠키 인증") + @ApiResponse(responseCode = "200", description = "쿠키 인증 성공") + @ApiErrorResponse( + status = HttpStatus.UNAUTHORIZED, + instance = "/login/check", + errorCases = {@ErrorCase( + description = "쿠키 값 오류", + exampleMessage = "인증에 실패했습니다." + )} + ) + void checkLogin(HttpServletRequest request); } diff --git a/backend/src/main/java/codezap/member/dto/LoginRequest.java b/backend/src/main/java/codezap/member/dto/LoginRequest.java index 13c0fca74..9d99f1936 100644 --- a/backend/src/main/java/codezap/member/dto/LoginRequest.java +++ b/backend/src/main/java/codezap/member/dto/LoginRequest.java @@ -1,4 +1,27 @@ package codezap.member.dto; -public record LoginRequest(String email, String password) { +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record LoginRequest( + @Schema( + description = "이메일", + example = "code@zap.com", + pattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$\n" + ) + @NotEmpty(message = "이메일이 입력되지 않았습니다.") + @Size(max = 255, message = "이메일은 255자 이하로 입력해주세요.") + String email, + + @Schema( + description = "비밀번호. 영어와 숫자를 반드시 포함해야 합니다.", + example = "password1234", + pattern = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>]).{8,}$" + ) + @NotEmpty(message = "비밀번호가 입력되지 않았습니다.") + @Size(min = 8, max = 255, message = "비밀번호는 8~16자 사이로 입력해주세요.") + String password +) { } diff --git a/backend/src/main/java/codezap/member/dto/SignupRequest.java b/backend/src/main/java/codezap/member/dto/SignupRequest.java index e10639066..7a9ec4899 100644 --- a/backend/src/main/java/codezap/member/dto/SignupRequest.java +++ b/backend/src/main/java/codezap/member/dto/SignupRequest.java @@ -1,8 +1,27 @@ package codezap.member.dto; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +import io.swagger.v3.oas.annotations.media.Schema; + public record SignupRequest( + @Schema(description = "이메일", example = "code@zap.com") + @Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "이메일 형식이 아닙니다.") + @NotEmpty(message = "이메일이 입력되지 않았습니다.") + @Size(max = 255, message = "이메일은 255자 이하로 입력해주세요.") String email, + + @Schema(description = "비밀번호. 영어와 숫자를 반드시 포함해야 합니다.", example = "password1234") + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>]).{8,}$", message = "영어와 숫자를 포함해야합니다.") + @NotEmpty(message = "비밀번호가 입력되지 않았습니다.") + @Size(min = 8, max = 255, message = "비밀번호는 8~16자 사이로 입력해주세요.") String password, + + @Schema(description = "사용자명", example = "zappy") + @NotEmpty(message = "사용자명이 입력되지 않았습니다.") + @Size(min = 2, max = 255, message = "사용자명은 2~255자 사이로 입력해주세요.") String username ) { } From 98d2da8a2cd6927435965fd554497f07607ee05c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Mon, 5 Aug 2024 14:58:56 +0900 Subject: [PATCH 067/168] =?UTF-8?q?feat(controller):=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=95=84=EC=9B=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 13 +++++++ .../controller/SpringDocMemberController.java | 38 +++++++++++++------ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/codezap/member/controller/MemberController.java b/backend/src/main/java/codezap/member/controller/MemberController.java index 8496e7562..430115838 100644 --- a/backend/src/main/java/codezap/member/controller/MemberController.java +++ b/backend/src/main/java/codezap/member/controller/MemberController.java @@ -66,4 +66,17 @@ public void login(@Valid @RequestBody LoginRequest request, HttpServletResponse public void checkLogin(HttpServletRequest request) { memberService.checkLogin(request.getCookies()); } + + + @PostMapping("/logout") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void logout(HttpServletResponse response) { + ResponseCookie cookie = ResponseCookie.from(HttpHeaders.AUTHORIZATION, "") + .maxAge(0) + .path("/") + .secure(true) + .httpOnly(true) + .build(); + response.setHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + } } diff --git a/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java b/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java index bde2fe8a5..8c1be6dd7 100644 --- a/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java +++ b/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java @@ -161,7 +161,7 @@ public interface SpringDocMemberController { @ApiResponse( responseCode = "200", description = "로그인 성공", - headers = {@Header(name = "Cookie", description = "base64(${email}:${password})")} + headers = {@Header(name = "Set-Cookie", description = "base64(${email}:${password}); path=\"/\"; HttpOnly; Secure;")} ) @ApiResponse( responseCode = "400", @@ -241,7 +241,7 @@ public interface SpringDocMemberController { @ExampleObject(name = "이메일 불일치", value = """ { "type": "about:blank", - "title": "BAD_REQUEST", + "title": "UNAUTHORIZED", "status": 401, "detail": "이메일이 입력되지 않았습니다.", "instance": "/login" @@ -251,7 +251,7 @@ public interface SpringDocMemberController { @ExampleObject(name = "비밀번호 불일치", value = """ { "type": "about:blank", - "title": "BAD_REQUEST", + "title": "UNAUTHORIZED", "status": 401, "detail": "비밀번호가 입력되지 않았습니다.", "instance": "/login" @@ -261,22 +261,36 @@ public interface SpringDocMemberController { } ) ) - @ApiErrorResponse( - status = HttpStatus.UNAUTHORIZED, - instance = "/login", - errorCases = {@ErrorCase(description = "이메일 또는 비밀번호 불일치", exampleMessage = "인증에 실패했습니다.")} - ) void login(LoginRequest request, HttpServletResponse response); @Operation(summary = "이메일 로그인 후 쿠키 인증") @ApiResponse(responseCode = "200", description = "쿠키 인증 성공") + @ApiResponse( + responseCode = "401", + description = "인증 실패", + content = @Content( + schema = @Schema(implementation = ProblemDetailSchema.class), + examples = { + @ExampleObject(name = "쿠키 값 오류", value = """ + { + "type": "about:blank", + "title": "UNAUTHORIZED", + "status": 401, + "detail": "인증에 실패했습니다.", + "instance": "/login" + } + """) + } + ) + ) @ApiErrorResponse( status = HttpStatus.UNAUTHORIZED, instance = "/login/check", - errorCases = {@ErrorCase( - description = "쿠키 값 오류", - exampleMessage = "인증에 실패했습니다." - )} + errorCases = {@ErrorCase(description = "쿠키 값 오류", exampleMessage = "인증에 실패했습니다.")} ) void checkLogin(HttpServletRequest request); + + @Operation(summary = "로그아웃") + @ApiResponse(responseCode = "204", description = "인증 성공") + void logout(HttpServletResponse response); } From 970a3f8bd6ac7a463166d4ffe30e0e95f1d54379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Mon, 5 Aug 2024 15:40:14 +0900 Subject: [PATCH 068/168] =?UTF-8?q?test(service):=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=A3=BC=EC=84=9D=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/codezap/member/service/MemberServiceTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/test/java/codezap/member/service/MemberServiceTest.java b/backend/src/test/java/codezap/member/service/MemberServiceTest.java index 14b1903bc..cb2f41cdb 100644 --- a/backend/src/test/java/codezap/member/service/MemberServiceTest.java +++ b/backend/src/test/java/codezap/member/service/MemberServiceTest.java @@ -74,15 +74,12 @@ class LoginTest { @Test @DisplayName("로그인 성공") void login() { - // given var member = new Member(1L, "code@zap.com", "pw1234", "zappy"); memberRepository.save(member); var request = new LoginRequest(member.getEmail(), member.getPassword()); - // when var actual = sut.login(request); - // then var expect = MemberDto.from(member); assertThat(actual).isEqualTo(expect); } From 145f9a880d4f9285605ac54d6881169640770f74 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Sun, 4 Aug 2024 11:45:54 +0900 Subject: [PATCH 069/168] =?UTF-8?q?feat(service):=20=EC=8A=A4=EB=8B=88?= =?UTF-8?q?=ED=8E=AB=20=EC=88=9C=EC=84=9C=20=EA=B2=80=EC=A6=9D=20groups=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/category/controller/CategoryController.java | 8 ++++---- .../java/codezap/global/validation/ValidationGroups.java | 1 + .../codezap/global/validation/ValidationSequence.java | 3 ++- .../validation/ValidatedSnippetsOrdinalRequest.java | 4 +++- .../category/controller/CategoryControllerTest.java | 3 ++- .../codezap/category/service/CategoryServiceTest.java | 1 + 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 8a1869652..9017cd667 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -2,9 +2,8 @@ import java.net.URI; -import jakarta.validation.Valid; - import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -18,6 +17,7 @@ import codezap.category.dto.request.UpdateCategoryRequest; import codezap.category.dto.response.FindAllCategoriesResponse; import codezap.category.service.CategoryService; +import codezap.global.validation.ValidationSequence; @RestController @RequestMapping("/categories") @@ -30,7 +30,7 @@ public CategoryController(CategoryService categoryService) { } @PostMapping - public ResponseEntity createCategory(@Valid @RequestBody CreateCategoryRequest createCategoryRequest) { + public ResponseEntity createCategory(@Validated(ValidationSequence.class) @RequestBody CreateCategoryRequest createCategoryRequest) { return ResponseEntity.created(URI.create("/categories/" + categoryService.create(createCategoryRequest))) .build(); } @@ -43,7 +43,7 @@ public ResponseEntity getCategories() { @PutMapping("/{id}") public ResponseEntity updateCategory( @PathVariable Long id, - @Valid @RequestBody UpdateCategoryRequest updateCategoryRequest + @Validated(ValidationSequence.class) @RequestBody UpdateCategoryRequest updateCategoryRequest ) { categoryService.update(id, updateCategoryRequest); return ResponseEntity.ok().build(); diff --git a/backend/src/main/java/codezap/global/validation/ValidationGroups.java b/backend/src/main/java/codezap/global/validation/ValidationGroups.java index ac49b4443..18fe8a044 100644 --- a/backend/src/main/java/codezap/global/validation/ValidationGroups.java +++ b/backend/src/main/java/codezap/global/validation/ValidationGroups.java @@ -2,6 +2,7 @@ public class ValidationGroups { public interface NotNullGroup {} + public interface SnippetOrdinalGroup {} public interface ByteLengthGroup {} public interface SizeCheckGroup {} } diff --git a/backend/src/main/java/codezap/global/validation/ValidationSequence.java b/backend/src/main/java/codezap/global/validation/ValidationSequence.java index 611530bfc..db331d574 100644 --- a/backend/src/main/java/codezap/global/validation/ValidationSequence.java +++ b/backend/src/main/java/codezap/global/validation/ValidationSequence.java @@ -5,7 +5,8 @@ import codezap.global.validation.ValidationGroups.ByteLengthGroup; import codezap.global.validation.ValidationGroups.NotNullGroup; import codezap.global.validation.ValidationGroups.SizeCheckGroup; +import codezap.global.validation.ValidationGroups.SnippetOrdinalGroup; -@GroupSequence({NotNullGroup.class, SizeCheckGroup.class, ByteLengthGroup.class}) +@GroupSequence({NotNullGroup.class, SnippetOrdinalGroup.class, ByteLengthGroup.class, SizeCheckGroup.class}) public interface ValidationSequence { } \ No newline at end of file diff --git a/backend/src/main/java/codezap/template/dto/request/validation/ValidatedSnippetsOrdinalRequest.java b/backend/src/main/java/codezap/template/dto/request/validation/ValidatedSnippetsOrdinalRequest.java index 5adb86fc6..7a09a5f06 100644 --- a/backend/src/main/java/codezap/template/dto/request/validation/ValidatedSnippetsOrdinalRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/validation/ValidatedSnippetsOrdinalRequest.java @@ -2,7 +2,9 @@ import java.util.List; -@SnippetsOrdinal(message = "스니펫 순서가 잘못되었습니다.") +import codezap.global.validation.ValidationGroups.SnippetOrdinalGroup; + +@SnippetsOrdinal(message = "스니펫 순서가 잘못되었습니다.", groups = SnippetOrdinalGroup.class) public interface ValidatedSnippetsOrdinalRequest { List extractSnippetsOrdinal(); diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index 50424a1d1..e99ed71c3 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -159,7 +159,8 @@ class deleteCategoryTest { @BeforeEach void saveCategory() { - savedCategoryId = categoryService.create(new CreateCategoryRequest("category1")); + categoryService.create(new CreateCategoryRequest("category1")); + savedCategoryId = categoryService.create(new CreateCategoryRequest("category2")); } @Test diff --git a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java index 9d7bd91af..5dd77c66a 100644 --- a/backend/src/test/java/codezap/category/service/CategoryServiceTest.java +++ b/backend/src/test/java/codezap/category/service/CategoryServiceTest.java @@ -94,6 +94,7 @@ void updateCategorySuccess() { @DisplayName("카테고리 삭제 성공") void deleteCategorySuccess() { // given + categoryRepository.save(new Category("category1")); Category savedCategory = categoryRepository.save(new Category("category1")); // when From e59816e9c842869f6da810798a1d5a32387168a8 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Sun, 4 Aug 2024 12:04:36 +0900 Subject: [PATCH 070/168] =?UTF-8?q?refactor(validation):=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/codezap/global/validation/ValidationSequence.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/global/validation/ValidationSequence.java b/backend/src/main/java/codezap/global/validation/ValidationSequence.java index db331d574..6c92a010d 100644 --- a/backend/src/main/java/codezap/global/validation/ValidationSequence.java +++ b/backend/src/main/java/codezap/global/validation/ValidationSequence.java @@ -7,6 +7,6 @@ import codezap.global.validation.ValidationGroups.SizeCheckGroup; import codezap.global.validation.ValidationGroups.SnippetOrdinalGroup; -@GroupSequence({NotNullGroup.class, SnippetOrdinalGroup.class, ByteLengthGroup.class, SizeCheckGroup.class}) +@GroupSequence({NotNullGroup.class, SizeCheckGroup.class, ByteLengthGroup.class, SnippetOrdinalGroup.class}) public interface ValidationSequence { } \ No newline at end of file From dbd59004085196da62b70e32a4b5cb3e129f2610 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Sun, 4 Aug 2024 14:51:08 +0900 Subject: [PATCH 071/168] =?UTF-8?q?feat(codezap):=20=EB=B9=88=20=EA=B0=92?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EA=B2=80=EC=A6=9D=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/dto/request/CreateCategoryRequest.java | 4 ++-- .../category/dto/request/UpdateCategoryRequest.java | 4 ++-- .../template/dto/request/CreateSnippetRequest.java | 5 +++-- .../template/dto/request/CreateTemplateRequest.java | 3 ++- .../template/dto/request/UpdateSnippetRequest.java | 5 +++-- .../java/codezap/template/service/TemplateService.java | 10 ++++++++++ 6 files changed, 22 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java index 5fefb1e3c..9587c9d04 100644 --- a/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java +++ b/backend/src/main/java/codezap/category/dto/request/CreateCategoryRequest.java @@ -1,6 +1,6 @@ package codezap.category.dto.request; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import codezap.global.validation.ValidationGroups.NotNullGroup; @@ -9,7 +9,7 @@ public record CreateCategoryRequest( @Schema(description = "카테고리 이름", example = "Spring") - @NotNull(message = "카테고리 이름이 null 입니다.", groups = NotNullGroup.class) + @NotBlank(message = "카테고리 이름이 null 입니다.", groups = NotNullGroup.class) @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String name ) { diff --git a/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java index 11149ba5c..52362219b 100644 --- a/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java +++ b/backend/src/main/java/codezap/category/dto/request/UpdateCategoryRequest.java @@ -1,6 +1,6 @@ package codezap.category.dto.request; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import codezap.global.validation.ValidationGroups.NotNullGroup; @@ -9,7 +9,7 @@ public record UpdateCategoryRequest( @Schema(description = "카테고리 이름", example = "Spring") - @NotNull(message = "카테고리 이름이 null 입니다.", groups = NotNullGroup.class) + @NotBlank(message = "카테고리 이름이 null 입니다.", groups = NotNullGroup.class) @Size(max = 255, message = "카테고리 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String name ) { diff --git a/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java index d90baa5db..24666c7c4 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java @@ -1,5 +1,6 @@ package codezap.template.dto.request; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -11,12 +12,12 @@ public record CreateSnippetRequest( @Schema(description = "파일 이름", example = "Main.java") - @NotNull(message = "파일 이름이 null 입니다.", groups = NotNullGroup.class) + @NotBlank(message = "파일 이름이 null 입니다.", groups = NotNullGroup.class) @Size(max = 255, message = "파일 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String filename, @Schema(description = "소스 코드", example = "public class Main { // ...") - @NotNull(message = "파일 내용이 null 입니다.", groups = NotNullGroup.class) + @NotBlank(message = "파일 내용이 null 입니다.", groups = NotNullGroup.class) @ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.", groups = ByteLengthGroup.class) String content, diff --git a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java index 515f79185..1d78b0da5 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java @@ -3,6 +3,7 @@ import java.util.List; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -15,7 +16,7 @@ public record CreateTemplateRequest( @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") - @NotNull(message = "템플릿 이름이 null 입니다.", groups = NotNullGroup.class) + @NotBlank(message = "템플릿 이름이 null 입니다.", groups = NotNullGroup.class) @Size(max = 255, message = "템플릿 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String title, diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java index 137d6ebec..923073424 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java @@ -1,5 +1,6 @@ package codezap.template.dto.request; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -15,12 +16,12 @@ public record UpdateSnippetRequest( Long id, @Schema(description = "파일 이름", example = "Main.java") - @NotNull(message = "파일 이름이 null 입니다.", groups = NotNullGroup.class) + @NotBlank(message = "파일 이름이 null 입니다.", groups = NotNullGroup.class) @Size(max = 255, message = "파일 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String filename, @Schema(description = "소스 코드", example = "public class Main { // ...") - @NotNull(message = "파일 내용이 null 입니다.", groups = NotNullGroup.class) + @NotBlank(message = "파일 내용이 null 입니다.", groups = NotNullGroup.class) @ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.", groups = ByteLengthGroup.class) String content, diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 11534317b..89c4a8f2e 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -3,11 +3,13 @@ import java.util.List; import java.util.Objects; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import codezap.category.domain.Category; import codezap.category.repository.CategoryRepository; +import codezap.global.exception.CodeZapException; import codezap.template.domain.Snippet; import codezap.template.domain.Tag; import codezap.template.domain.Template; @@ -93,6 +95,7 @@ public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) template.updateTemplate(updateTemplateRequest.title(), updateTemplateRequest.description(), category); updateSnippets(updateTemplateRequest, template); updateTags(updateTemplateRequest, template); + validateSnippetsCount(updateTemplateRequest, template); } private void updateSnippets(UpdateTemplateRequest updateTemplateRequest, Template template) { @@ -122,6 +125,13 @@ private void updateTags(UpdateTemplateRequest updateTemplateRequest, Template te tags.forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); } + private void validateSnippetsCount(UpdateTemplateRequest updateTemplateRequest, Template template) { + if (updateTemplateRequest.updateSnippets().size() + updateTemplateRequest.createSnippets().size() + != snippetRepository.findAllByTemplate(template).size()) { + throw new CodeZapException(HttpStatus.BAD_REQUEST, "스니펫의 정보가 정확하지 않습니다."); + } + } + @Transactional public void deleteById(Long id) { thumbnailSnippetRepository.deleteByTemplateId(id); From b267eea4cd8fa25b799a876a95f1a1eb98f4b36c Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Sun, 4 Aug 2024 18:22:32 +0900 Subject: [PATCH 072/168] =?UTF-8?q?feat(codezap):=20=EC=8A=A4=EB=8B=88?= =?UTF-8?q?=ED=8E=AB=EC=9D=B4=20=EB=B9=84=EC=96=B4=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=EC=97=90=20=EB=8C=80=ED=95=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/validation/ValidationGroups.java | 1 + .../global/validation/ValidationSequence.java | 3 ++- .../dto/request/CreateTemplateRequest.java | 1 + .../dto/request/UpdateTemplateRequest.java | 11 ++++++++-- .../dto/request/validation/SnippetsCount.java | 21 +++++++++++++++++++ .../validation/SnippetsCountValidator.java | 15 +++++++++++++ .../ValidatedSnippetsCountRequest.java | 9 ++++++++ 7 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/codezap/template/dto/request/validation/SnippetsCount.java create mode 100644 backend/src/main/java/codezap/template/dto/request/validation/SnippetsCountValidator.java create mode 100644 backend/src/main/java/codezap/template/dto/request/validation/ValidatedSnippetsCountRequest.java diff --git a/backend/src/main/java/codezap/global/validation/ValidationGroups.java b/backend/src/main/java/codezap/global/validation/ValidationGroups.java index 18fe8a044..f78603d88 100644 --- a/backend/src/main/java/codezap/global/validation/ValidationGroups.java +++ b/backend/src/main/java/codezap/global/validation/ValidationGroups.java @@ -3,6 +3,7 @@ public class ValidationGroups { public interface NotNullGroup {} public interface SnippetOrdinalGroup {} + public interface SnippetCountGroup {} public interface ByteLengthGroup {} public interface SizeCheckGroup {} } diff --git a/backend/src/main/java/codezap/global/validation/ValidationSequence.java b/backend/src/main/java/codezap/global/validation/ValidationSequence.java index 6c92a010d..25cb9ef26 100644 --- a/backend/src/main/java/codezap/global/validation/ValidationSequence.java +++ b/backend/src/main/java/codezap/global/validation/ValidationSequence.java @@ -5,8 +5,9 @@ import codezap.global.validation.ValidationGroups.ByteLengthGroup; import codezap.global.validation.ValidationGroups.NotNullGroup; import codezap.global.validation.ValidationGroups.SizeCheckGroup; +import codezap.global.validation.ValidationGroups.SnippetCountGroup; import codezap.global.validation.ValidationGroups.SnippetOrdinalGroup; -@GroupSequence({NotNullGroup.class, SizeCheckGroup.class, ByteLengthGroup.class, SnippetOrdinalGroup.class}) +@GroupSequence({NotNullGroup.class, SizeCheckGroup.class, ByteLengthGroup.class, SnippetCountGroup.class, SnippetOrdinalGroup.class}) public interface ValidationSequence { } \ No newline at end of file diff --git a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java index 1d78b0da5..a5c273e34 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java @@ -27,6 +27,7 @@ public record CreateTemplateRequest( @Schema(description = "템플릿의 스니펫 내역") @NotNull(message = "스니펫 리스트가 null 입니다.", groups = NotNullGroup.class) + @Size(min = 1, message = "스니펫은 최소 1개 입력해야 합니다.", groups = SizeCheckGroup.class) @Valid List snippets, diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java index b87d3da5a..e8df2bed1 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java @@ -4,6 +4,7 @@ import java.util.stream.Stream; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -11,12 +12,13 @@ import codezap.global.validation.ValidationGroups.ByteLengthGroup; import codezap.global.validation.ValidationGroups.NotNullGroup; import codezap.global.validation.ValidationGroups.SizeCheckGroup; +import codezap.template.dto.request.validation.ValidatedSnippetsCountRequest; import codezap.template.dto.request.validation.ValidatedSnippetsOrdinalRequest; import io.swagger.v3.oas.annotations.media.Schema; public record UpdateTemplateRequest( @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") - @NotNull(message = "템플릿 이름이 null 입니다.", groups = NotNullGroup.class) + @NotBlank(message = "템플릿 이름이 null 입니다.", groups = NotNullGroup.class) @Size(max = 255, message = "템플릿 이름은 최대 255자까지 입력 가능합니다.", groups = SizeCheckGroup.class) String title, @@ -46,7 +48,7 @@ public record UpdateTemplateRequest( @Schema(description = "태그 리스트") @NotNull(message = "태그 리스트가 null 입니다.") List tags -) implements ValidatedSnippetsOrdinalRequest { +) implements ValidatedSnippetsOrdinalRequest, ValidatedSnippetsCountRequest { @Override public List extractSnippetsOrdinal() { return Stream.concat( @@ -54,4 +56,9 @@ public List extractSnippetsOrdinal() { createSnippets.stream().map(CreateSnippetRequest::ordinal) ).toList(); } + + @Override + public Integer countSnippets() { + return updateSnippets.size() + createSnippets.size(); + } } diff --git a/backend/src/main/java/codezap/template/dto/request/validation/SnippetsCount.java b/backend/src/main/java/codezap/template/dto/request/validation/SnippetsCount.java new file mode 100644 index 000000000..86f41a911 --- /dev/null +++ b/backend/src/main/java/codezap/template/dto/request/validation/SnippetsCount.java @@ -0,0 +1,21 @@ +package codezap.template.dto.request.validation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = SnippetsCountValidator.class) +public @interface SnippetsCount { + + String message(); + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/backend/src/main/java/codezap/template/dto/request/validation/SnippetsCountValidator.java b/backend/src/main/java/codezap/template/dto/request/validation/SnippetsCountValidator.java new file mode 100644 index 000000000..a68729f6d --- /dev/null +++ b/backend/src/main/java/codezap/template/dto/request/validation/SnippetsCountValidator.java @@ -0,0 +1,15 @@ +package codezap.template.dto.request.validation; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class SnippetsCountValidator implements ConstraintValidator { + + @Override + public boolean isValid(ValidatedSnippetsCountRequest validatedSnippetsCountRequest, + ConstraintValidatorContext constraintValidatorContext + ) { + Integer snippetsCount = validatedSnippetsCountRequest.countSnippets(); + return snippetsCount > 0; + } +} diff --git a/backend/src/main/java/codezap/template/dto/request/validation/ValidatedSnippetsCountRequest.java b/backend/src/main/java/codezap/template/dto/request/validation/ValidatedSnippetsCountRequest.java new file mode 100644 index 000000000..2de66a8b7 --- /dev/null +++ b/backend/src/main/java/codezap/template/dto/request/validation/ValidatedSnippetsCountRequest.java @@ -0,0 +1,9 @@ +package codezap.template.dto.request.validation; + +import codezap.global.validation.ValidationGroups.SnippetCountGroup; + +@SnippetsCount(message = "스니펫은 최소 1개 입력해야 합니다.", groups = SnippetCountGroup.class) +public interface ValidatedSnippetsCountRequest { + + Integer countSnippets(); +} From 094b5b780da9c656f4bd8b0ef48caa737a558d9d Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 15:41:49 +0900 Subject: [PATCH 073/168] =?UTF-8?q?refactor(category):=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B0=80=EB=8F=85=EC=84=B1=20=ED=96=A5=EC=83=81?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/controller/CategoryController.java | 7 +++++-- .../controller/SpringDocCategoryController.java | 3 +-- .../codezap/category/service/CategoryService.java | 12 ++++++------ .../codezap/global/validation/ValidationGroups.java | 4 ++++ .../global/validation/ValidationSequence.java | 7 ++++++- .../template/controller/TemplateController.java | 8 +++++--- 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 9017cd667..0ca9511bc 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -30,8 +30,11 @@ public CategoryController(CategoryService categoryService) { } @PostMapping - public ResponseEntity createCategory(@Validated(ValidationSequence.class) @RequestBody CreateCategoryRequest createCategoryRequest) { - return ResponseEntity.created(URI.create("/categories/" + categoryService.create(createCategoryRequest))) + public ResponseEntity createCategory( + @Validated(ValidationSequence.class) @RequestBody CreateCategoryRequest createCategoryRequest + ) { + Long createdCategoryId = categoryService.create(createCategoryRequest); + return ResponseEntity.created(URI.create("/categories/" + createdCategoryId)) .build(); } diff --git a/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java b/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java index f3ad05bdf..c288e47d1 100644 --- a/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java +++ b/backend/src/main/java/codezap/category/controller/SpringDocCategoryController.java @@ -27,8 +27,7 @@ public interface SpringDocCategoryController { @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/categories", errorCases = { @ErrorCase(description = "모든 필드 중 null인 값이 있는 경우", exampleMessage = "카테고리 이름이 null 입니다."), @ErrorCase(description = "카테고리 이름이 255자를 초과한 경우", exampleMessage = "카테고리 이름은 최대 255자까지 입력 가능합니다."), - @ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", - exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다.") + @ErrorCase(description = "동일한 이름의 카테고리가 존재하는 경우", exampleMessage = "이름이 Spring 인 카테고리가 이미 존재합니다.") }) ResponseEntity createCategory(CreateCategoryRequest createCategoryRequest); diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 1b5b9cdc1..4b5a59da4 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -42,6 +42,12 @@ public void update(Long id, UpdateCategoryRequest updateCategoryRequest) { category.updateName(updateCategoryRequest.name()); } + private void validateDuplicatedCategory(String categoryName) { + if (categoryRepository.existsByName(categoryName)) { + throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재합니다."); + } + } + public void deleteById(Long id) { if (templateRepository.existsByCategoryId(id)) { throw new CodeZapException(HttpStatus.BAD_REQUEST, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다."); @@ -51,10 +57,4 @@ public void deleteById(Long id) { } categoryRepository.deleteById(id); } - - private void validateDuplicatedCategory(String categoryName) { - if (categoryRepository.existsByName(categoryName)) { - throw new CodeZapException(HttpStatus.CONFLICT, "이름이 " + categoryName + "인 카테고리가 이미 존재합니다."); - } - } } diff --git a/backend/src/main/java/codezap/global/validation/ValidationGroups.java b/backend/src/main/java/codezap/global/validation/ValidationGroups.java index f78603d88..ba25f3892 100644 --- a/backend/src/main/java/codezap/global/validation/ValidationGroups.java +++ b/backend/src/main/java/codezap/global/validation/ValidationGroups.java @@ -2,8 +2,12 @@ public class ValidationGroups { public interface NotNullGroup {} + public interface SnippetOrdinalGroup {} + public interface SnippetCountGroup {} + public interface ByteLengthGroup {} + public interface SizeCheckGroup {} } diff --git a/backend/src/main/java/codezap/global/validation/ValidationSequence.java b/backend/src/main/java/codezap/global/validation/ValidationSequence.java index 25cb9ef26..8c8fb97ff 100644 --- a/backend/src/main/java/codezap/global/validation/ValidationSequence.java +++ b/backend/src/main/java/codezap/global/validation/ValidationSequence.java @@ -8,6 +8,11 @@ import codezap.global.validation.ValidationGroups.SnippetCountGroup; import codezap.global.validation.ValidationGroups.SnippetOrdinalGroup; -@GroupSequence({NotNullGroup.class, SizeCheckGroup.class, ByteLengthGroup.class, SnippetCountGroup.class, SnippetOrdinalGroup.class}) +@GroupSequence({ + NotNullGroup.class, + SizeCheckGroup.class, + ByteLengthGroup.class, + SnippetCountGroup.class, + SnippetOrdinalGroup.class}) public interface ValidationSequence { } \ No newline at end of file diff --git a/backend/src/main/java/codezap/template/controller/TemplateController.java b/backend/src/main/java/codezap/template/controller/TemplateController.java index dbd5ceec2..5a4cec80a 100644 --- a/backend/src/main/java/codezap/template/controller/TemplateController.java +++ b/backend/src/main/java/codezap/template/controller/TemplateController.java @@ -16,7 +16,7 @@ import codezap.template.dto.request.CreateTemplateRequest; import codezap.template.dto.request.UpdateTemplateRequest; import codezap.template.dto.response.FindAllTemplatesResponse; -import codezap.template.dto.response.FindTemplateByIdResponse; +import codezap.template.dto.response.FindTemplateResponse; import codezap.template.service.TemplateService; @RestController @@ -30,7 +30,9 @@ public TemplateController(TemplateService templateService) { } @PostMapping - public ResponseEntity create(@Validated(ValidationSequence.class) @RequestBody CreateTemplateRequest createTemplateRequest) { + public ResponseEntity create( + @Validated(ValidationSequence.class) @RequestBody CreateTemplateRequest createTemplateRequest + ) { return ResponseEntity.created(URI.create("/templates/" + templateService.create(createTemplateRequest))) .build(); } @@ -41,7 +43,7 @@ public ResponseEntity getTemplates() { } @GetMapping("/{id}") - public ResponseEntity getTemplateById(@PathVariable Long id) { + public ResponseEntity getTemplateById(@PathVariable Long id) { return ResponseEntity.ok(templateService.findById(id)); } From b8e19952989b1c50ef98d16f572c7aebb74d16ae Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 15:50:00 +0900 Subject: [PATCH 074/168] =?UTF-8?q?refactor(codezap):=20responseDto=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/FindAllCategoriesResponse.java | 4 ++-- ...Response.java => FindCategoryResponse.java} | 6 +++--- .../SpringDocTemplateController.java | 4 ++-- ...gByIdResponse.java => FindTagResponse.java} | 6 +++--- ...Response.java => FindTemplateResponse.java} | 18 +++++++++--------- .../template/service/TemplateService.java | 6 +++--- .../template/service/TemplateServiceTest.java | 4 ++-- 7 files changed, 24 insertions(+), 24 deletions(-) rename backend/src/main/java/codezap/category/dto/response/{FindCategoryByIdResponse.java => FindCategoryResponse.java} (62%) rename backend/src/main/java/codezap/template/dto/response/{FindTagByIdResponse.java => FindTagResponse.java} (66%) rename backend/src/main/java/codezap/template/dto/response/{FindTemplateByIdResponse.java => FindTemplateResponse.java} (75%) diff --git a/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java b/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java index b17e2168f..bac6dccf6 100644 --- a/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java +++ b/backend/src/main/java/codezap/category/dto/response/FindAllCategoriesResponse.java @@ -7,12 +7,12 @@ public record FindAllCategoriesResponse( @Schema(description = "카테고리 목록") - List categories + List categories ) { public static FindAllCategoriesResponse from(List categories) { return new FindAllCategoriesResponse( categories.stream() - .map(FindCategoryByIdResponse::from) + .map(FindCategoryResponse::from) .toList() ); } diff --git a/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java b/backend/src/main/java/codezap/category/dto/response/FindCategoryResponse.java similarity index 62% rename from backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java rename to backend/src/main/java/codezap/category/dto/response/FindCategoryResponse.java index 999a7a46a..699fda8d6 100644 --- a/backend/src/main/java/codezap/category/dto/response/FindCategoryByIdResponse.java +++ b/backend/src/main/java/codezap/category/dto/response/FindCategoryResponse.java @@ -3,13 +3,13 @@ import codezap.category.domain.Category; import io.swagger.v3.oas.annotations.media.Schema; -public record FindCategoryByIdResponse( +public record FindCategoryResponse( @Schema(description = "카테고리 식별자", example = "1") Long id, @Schema(description = "카테고리 이름", example = "Spring") String name ) { - public static FindCategoryByIdResponse from(Category category) { - return new FindCategoryByIdResponse(category.getId(), category.getName()); + public static FindCategoryResponse from(Category category) { + return new FindCategoryResponse(category.getId(), category.getName()); } } diff --git a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java index f125a25f6..cd7493a51 100644 --- a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java +++ b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java @@ -8,7 +8,7 @@ import codezap.template.dto.request.CreateTemplateRequest; import codezap.template.dto.request.UpdateTemplateRequest; import codezap.template.dto.response.FindAllTemplatesResponse; -import codezap.template.dto.response.FindTemplateByIdResponse; +import codezap.template.dto.response.FindTemplateResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; @@ -49,7 +49,7 @@ public interface SpringDocTemplateController { @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/templates/1", errorCases = { @ErrorCase(description = "해당하는 id 값인 템플릿이 없는 경우", exampleMessage = "식별자 1에 해당하는 템플릿이 존재하지 않습니다."), }) - ResponseEntity getTemplateById(Long id); + ResponseEntity getTemplateById(Long id); @Operation(summary = "템플릿 수정", description = "해당하는 식별자의 템플릿을 수정합니다.") @ApiResponse(responseCode = "200", description = "템플릿 수정 성공") diff --git a/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java b/backend/src/main/java/codezap/template/dto/response/FindTagResponse.java similarity index 66% rename from backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java rename to backend/src/main/java/codezap/template/dto/response/FindTagResponse.java index 60d6a9173..08a167a37 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindTagByIdResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindTagResponse.java @@ -3,14 +3,14 @@ import codezap.template.domain.Tag; import io.swagger.v3.oas.annotations.media.Schema; -public record FindTagByIdResponse( +public record FindTagResponse( @Schema(description = "태그 식별자", example = "1") Long id, @Schema(description = "태그 이름", example = "스프링") String name ) { - public static FindTagByIdResponse from(Tag tag) { - return new FindTagByIdResponse(tag.getId(), tag.getName()); + public static FindTagResponse from(Tag tag) { + return new FindTagResponse(tag.getId(), tag.getName()); } } diff --git a/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java b/backend/src/main/java/codezap/template/dto/response/FindTemplateResponse.java similarity index 75% rename from backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java rename to backend/src/main/java/codezap/template/dto/response/FindTemplateResponse.java index b9451b777..3920cfdd8 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindTemplateByIdResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindTemplateResponse.java @@ -3,13 +3,13 @@ import java.time.LocalDateTime; import java.util.List; -import codezap.category.dto.response.FindCategoryByIdResponse; +import codezap.category.dto.response.FindCategoryResponse; import codezap.template.domain.Snippet; import codezap.template.domain.Tag; import codezap.template.domain.Template; import io.swagger.v3.oas.annotations.media.Schema; -public record FindTemplateByIdResponse( +public record FindTemplateResponse( @Schema(description = "템플릿 식별자", example = "0") Long id, @@ -23,21 +23,21 @@ public record FindTemplateByIdResponse( List snippets, @Schema(description = "카테고리 정보") - FindCategoryByIdResponse category, + FindCategoryResponse category, @Schema(description = "태그 목록") - List tags, + List tags, @Schema(description = "템플릿 수정 시간", example = "2024-11-11 12:00", type = "string") LocalDateTime modifiedAt ) { - public static FindTemplateByIdResponse of(Template template, List snippets, List tags) { - return new FindTemplateByIdResponse( + public static FindTemplateResponse of(Template template, List snippets, List tags) { + return new FindTemplateResponse( template.getId(), template.getTitle(), template.getDescription(), mapToFindAllSnippetByTemplateResponse(snippets), - FindCategoryByIdResponse.from(template.getCategory()), + FindCategoryResponse.from(template.getCategory()), mapToFindTagByTemplateResponse(tags), template.getModifiedAt() ); @@ -51,11 +51,11 @@ private static List mapToFindAllSnippetByTempl .toList(); } - private static List mapToFindTagByTemplateResponse( + private static List mapToFindTagByTemplateResponse( List tags ) { return tags.stream() - .map(FindTagByIdResponse::from) + .map(FindTagResponse::from) .toList(); } } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 89c4a8f2e..19c23dd79 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -20,7 +20,7 @@ import codezap.template.dto.request.UpdateSnippetRequest; import codezap.template.dto.request.UpdateTemplateRequest; import codezap.template.dto.response.FindAllTemplatesResponse; -import codezap.template.dto.response.FindTemplateByIdResponse; +import codezap.template.dto.response.FindTemplateResponse; import codezap.template.repository.SnippetRepository; import codezap.template.repository.TagRepository; import codezap.template.repository.TemplateRepository; @@ -79,13 +79,13 @@ public FindAllTemplatesResponse findAll() { return FindAllTemplatesResponse.from(thumbnailSnippetRepository.findAll()); } - public FindTemplateByIdResponse findById(Long id) { + public FindTemplateResponse findById(Long id) { Template template = templateRepository.fetchById(id); List snippets = snippetRepository.findAllByTemplate(template); List tags = templateTagRepository.findAllByTemplate(template).stream() .map(TemplateTag::getTag) .toList(); - return FindTemplateByIdResponse.of(template, snippets, tags); + return FindTemplateResponse.of(template, snippets, tags); } @Transactional diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index af3669a35..728e046b5 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -27,7 +27,7 @@ import codezap.template.dto.request.UpdateSnippetRequest; import codezap.template.dto.request.UpdateTemplateRequest; import codezap.template.dto.response.FindAllTemplatesResponse; -import codezap.template.dto.response.FindTemplateByIdResponse; +import codezap.template.dto.response.FindTemplateResponse; import codezap.template.repository.SnippetRepository; import codezap.template.repository.TagRepository; import codezap.template.repository.TemplateRepository; @@ -109,7 +109,7 @@ void findOneTemplateSuccess() { Template template = saveTemplate(createdTemplate); // when - FindTemplateByIdResponse foundTemplate = templateService.findById(template.getId()); + FindTemplateResponse foundTemplate = templateService.findById(template.getId()); // then assertAll( From 04356e038b8eb6968b5442ce570c22f2d267c44c Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 15:53:03 +0900 Subject: [PATCH 075/168] =?UTF-8?q?refactor(codezap):=20NoArgsConstructor?= =?UTF-8?q?=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=EC=96=B4=EC=9E=90=20protected?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/codezap/category/domain/Category.java | 3 ++- backend/src/main/java/codezap/member/domain/Member.java | 3 ++- backend/src/main/java/codezap/template/domain/Snippet.java | 3 ++- backend/src/main/java/codezap/template/domain/Tag.java | 3 ++- backend/src/main/java/codezap/template/domain/Template.java | 3 ++- .../src/main/java/codezap/template/domain/TemplateTag.java | 5 +++-- .../main/java/codezap/template/domain/ThumbnailSnippet.java | 3 ++- 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/codezap/category/domain/Category.java b/backend/src/main/java/codezap/category/domain/Category.java index b44c07ddb..df1818d5e 100644 --- a/backend/src/main/java/codezap/category/domain/Category.java +++ b/backend/src/main/java/codezap/category/domain/Category.java @@ -7,11 +7,12 @@ import jakarta.persistence.Id; import codezap.global.auditing.BaseTimeEntity; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class Category extends BaseTimeEntity { @Id diff --git a/backend/src/main/java/codezap/member/domain/Member.java b/backend/src/main/java/codezap/member/domain/Member.java index 711092d33..8023e2991 100644 --- a/backend/src/main/java/codezap/member/domain/Member.java +++ b/backend/src/main/java/codezap/member/domain/Member.java @@ -6,12 +6,13 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Getter -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Member { @Id diff --git a/backend/src/main/java/codezap/template/domain/Snippet.java b/backend/src/main/java/codezap/template/domain/Snippet.java index c85b0ee36..a70a74e77 100644 --- a/backend/src/main/java/codezap/template/domain/Snippet.java +++ b/backend/src/main/java/codezap/template/domain/Snippet.java @@ -12,11 +12,12 @@ import jakarta.persistence.ManyToOne; import codezap.global.auditing.BaseTimeEntity; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class Snippet extends BaseTimeEntity { diff --git a/backend/src/main/java/codezap/template/domain/Tag.java b/backend/src/main/java/codezap/template/domain/Tag.java index eb7bf6fd7..7d6eb3d51 100644 --- a/backend/src/main/java/codezap/template/domain/Tag.java +++ b/backend/src/main/java/codezap/template/domain/Tag.java @@ -7,11 +7,12 @@ import jakarta.persistence.Id; import codezap.global.auditing.BaseTimeEntity; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class Tag extends BaseTimeEntity { diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index 75aff1906..d4f17316c 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -9,11 +9,12 @@ import codezap.category.domain.Category; import codezap.global.auditing.BaseTimeEntity; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class Template extends BaseTimeEntity { diff --git a/backend/src/main/java/codezap/template/domain/TemplateTag.java b/backend/src/main/java/codezap/template/domain/TemplateTag.java index eefb2ca0b..02a94916d 100644 --- a/backend/src/main/java/codezap/template/domain/TemplateTag.java +++ b/backend/src/main/java/codezap/template/domain/TemplateTag.java @@ -10,18 +10,19 @@ import jakarta.persistence.MapsId; import codezap.global.auditing.BaseTimeEntity; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class TemplateTag extends BaseTimeEntity { @Embeddable - @NoArgsConstructor + @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Getter @EqualsAndHashCode diff --git a/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java b/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java index 920dc18d2..47664f625 100644 --- a/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java +++ b/backend/src/main/java/codezap/template/domain/ThumbnailSnippet.java @@ -7,11 +7,12 @@ import jakarta.persistence.OneToOne; import codezap.global.auditing.BaseTimeEntity; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class ThumbnailSnippet extends BaseTimeEntity { From 30717163cb7329dfa931b802db536268eff8527f Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 15:54:39 +0900 Subject: [PATCH 076/168] =?UTF-8?q?refactor(category):=20=EC=9D=98?= =?UTF-8?q?=EB=AF=B8=20=EC=9E=88=EB=8A=94=20=EC=88=AB=EC=9E=90=20=EC=83=81?= =?UTF-8?q?=EC=88=98=ED=99=94=20(=EA=B8=B0=EB=B3=B8=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=EC=9D=98=20id)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/codezap/category/service/CategoryService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 4b5a59da4..309d5e17b 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -15,6 +15,7 @@ @Service public class CategoryService { + public static final int DEFAULT_CATEGORY = 1; private final CategoryRepository categoryRepository; private final TemplateRepository templateRepository; @@ -52,8 +53,8 @@ public void deleteById(Long id) { if (templateRepository.existsByCategoryId(id)) { throw new CodeZapException(HttpStatus.BAD_REQUEST, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다."); } - if (id == 1) { - throw new CodeZapException(HttpStatus.BAD_REQUEST, "1번 카테고리는 삭제할 수 없습니다."); + if (id == DEFAULT_CATEGORY) { + throw new CodeZapException(HttpStatus.BAD_REQUEST, "기본 카테고리는 삭제할 수 없습니다."); } categoryRepository.deleteById(id); } From 96408edeb3fbf44a45708b5c40b19bc1d91e6d6b Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 16:00:35 +0900 Subject: [PATCH 077/168] =?UTF-8?q?refactor(template):=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codezap/template/repository/TemplateTagRepository.java | 2 -- .../src/main/java/codezap/template/service/TemplateService.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java index 41fe972d3..a0de2a4fb 100644 --- a/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TemplateTagRepository.java @@ -11,7 +11,5 @@ public interface TemplateTagRepository extends JpaRepository List findAllByTemplate(Template template); - void deleteAllByTemplate(Template template); - void deleteAllByTemplateId(Long id); } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 19c23dd79..c26a3346a 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -113,7 +113,7 @@ private void updateSnippets(UpdateTemplateRequest updateTemplateRequest, Templat } private void updateTags(UpdateTemplateRequest updateTemplateRequest, Template template) { - templateTagRepository.deleteAllByTemplate(template); + templateTagRepository.deleteAllByTemplateId(template.getId()); updateTemplateRequest.tags().stream() .map(Tag::new) .filter(tag -> !tagRepository.existsByName(tag.getName())) From 4165b92f5a70336ea1b53e055ea1481da5d38443 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 16:08:12 +0900 Subject: [PATCH 078/168] =?UTF-8?q?refactor(codezap):=20ValidationGroups?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B2=B9=EC=B9=98=EB=8A=94=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/codezap/global/validation/ValidationGroups.java | 2 -- .../java/codezap/global/validation/ValidationSequence.java | 4 +--- .../codezap/template/dto/request/CreateSnippetRequest.java | 3 +-- .../codezap/template/dto/request/CreateTemplateRequest.java | 3 +-- .../codezap/template/dto/request/UpdateSnippetRequest.java | 3 +-- .../codezap/template/dto/request/UpdateTemplateRequest.java | 3 +-- 6 files changed, 5 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/codezap/global/validation/ValidationGroups.java b/backend/src/main/java/codezap/global/validation/ValidationGroups.java index ba25f3892..327e62fbb 100644 --- a/backend/src/main/java/codezap/global/validation/ValidationGroups.java +++ b/backend/src/main/java/codezap/global/validation/ValidationGroups.java @@ -7,7 +7,5 @@ public interface SnippetOrdinalGroup {} public interface SnippetCountGroup {} - public interface ByteLengthGroup {} - public interface SizeCheckGroup {} } diff --git a/backend/src/main/java/codezap/global/validation/ValidationSequence.java b/backend/src/main/java/codezap/global/validation/ValidationSequence.java index 8c8fb97ff..c2da469db 100644 --- a/backend/src/main/java/codezap/global/validation/ValidationSequence.java +++ b/backend/src/main/java/codezap/global/validation/ValidationSequence.java @@ -2,7 +2,6 @@ import jakarta.validation.GroupSequence; -import codezap.global.validation.ValidationGroups.ByteLengthGroup; import codezap.global.validation.ValidationGroups.NotNullGroup; import codezap.global.validation.ValidationGroups.SizeCheckGroup; import codezap.global.validation.ValidationGroups.SnippetCountGroup; @@ -11,8 +10,7 @@ @GroupSequence({ NotNullGroup.class, SizeCheckGroup.class, - ByteLengthGroup.class, SnippetCountGroup.class, SnippetOrdinalGroup.class}) public interface ValidationSequence { -} \ No newline at end of file +} diff --git a/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java index 24666c7c4..cc527635d 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateSnippetRequest.java @@ -5,7 +5,6 @@ import jakarta.validation.constraints.Size; import codezap.global.validation.ByteLength; -import codezap.global.validation.ValidationGroups.ByteLengthGroup; import codezap.global.validation.ValidationGroups.NotNullGroup; import codezap.global.validation.ValidationGroups.SizeCheckGroup; import io.swagger.v3.oas.annotations.media.Schema; @@ -18,7 +17,7 @@ public record CreateSnippetRequest( @Schema(description = "소스 코드", example = "public class Main { // ...") @NotBlank(message = "파일 내용이 null 입니다.", groups = NotNullGroup.class) - @ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.", groups = ByteLengthGroup.class) + @ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.", groups = SizeCheckGroup.class) String content, @Schema(description = "스니펫 순서", example = "1") diff --git a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java index a5c273e34..dac09639e 100644 --- a/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/CreateTemplateRequest.java @@ -8,7 +8,6 @@ import jakarta.validation.constraints.Size; import codezap.global.validation.ByteLength; -import codezap.global.validation.ValidationGroups.ByteLengthGroup; import codezap.global.validation.ValidationGroups.NotNullGroup; import codezap.global.validation.ValidationGroups.SizeCheckGroup; import codezap.template.dto.request.validation.ValidatedSnippetsOrdinalRequest; @@ -22,7 +21,7 @@ public record CreateTemplateRequest( @Schema(description = "템플릿 설명", example = "JWT를 사용하여 로그인 기능을 구현함") @NotNull(message = "템플릿 설명이 null 입니다.", groups = NotNullGroup.class) - @ByteLength(max = 65_535, message = "템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.", groups = ByteLengthGroup.class) + @ByteLength(max = 65_535, message = "템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.", groups = SizeCheckGroup.class) String description, @Schema(description = "템플릿의 스니펫 내역") diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java index 923073424..bce59a6d8 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateSnippetRequest.java @@ -5,7 +5,6 @@ import jakarta.validation.constraints.Size; import codezap.global.validation.ByteLength; -import codezap.global.validation.ValidationGroups.ByteLengthGroup; import codezap.global.validation.ValidationGroups.NotNullGroup; import codezap.global.validation.ValidationGroups.SizeCheckGroup; import io.swagger.v3.oas.annotations.media.Schema; @@ -22,7 +21,7 @@ public record UpdateSnippetRequest( @Schema(description = "소스 코드", example = "public class Main { // ...") @NotBlank(message = "파일 내용이 null 입니다.", groups = NotNullGroup.class) - @ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.", groups = ByteLengthGroup.class) + @ByteLength(max = 65_535, message = "파일 내용은 최대 65,535 Byte까지 입력 가능합니다.", groups = SizeCheckGroup.class) String content, @Schema(description = "스니펫 순서", example = "1") diff --git a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java index e8df2bed1..e99232cfe 100644 --- a/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java +++ b/backend/src/main/java/codezap/template/dto/request/UpdateTemplateRequest.java @@ -9,7 +9,6 @@ import jakarta.validation.constraints.Size; import codezap.global.validation.ByteLength; -import codezap.global.validation.ValidationGroups.ByteLengthGroup; import codezap.global.validation.ValidationGroups.NotNullGroup; import codezap.global.validation.ValidationGroups.SizeCheckGroup; import codezap.template.dto.request.validation.ValidatedSnippetsCountRequest; @@ -24,7 +23,7 @@ public record UpdateTemplateRequest( @Schema(description = "템플릿 설명", example = "JWT를 사용하여 로그인 기능을 구현함") @NotNull(message = "템플릿 설명이 null 입니다.", groups = NotNullGroup.class) - @ByteLength(max = 65_535, message = "템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.", groups = ByteLengthGroup.class) + @ByteLength(max = 65_535, message = "템플릿 설명은 최대 65,535 Byte까지 입력 가능합니다.", groups = SizeCheckGroup.class) String description, @Schema(description = "새로 추가한 스니펫 내역") From e30fb4b05288a8b7812b0fa82696f21a0699807b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Mon, 5 Aug 2024 16:09:02 +0900 Subject: [PATCH 079/168] =?UTF-8?q?refactor(configuration):=20argument=20r?= =?UTF-8?q?esolver=EC=9D=98=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/member/configuration/AuthArgumentResolver.java | 6 +++--- .../codezap/member/configuration/AuthWebConfiguration.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java b/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java index 9e745a957..8803459c2 100644 --- a/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java +++ b/backend/src/main/java/codezap/member/configuration/AuthArgumentResolver.java @@ -8,13 +8,13 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; -import codezap.member.service.MemberService; +import codezap.member.service.AuthService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class AuthArgumentResolver implements HandlerMethodArgumentResolver { - private final MemberService memberService; + private final AuthService authService; @Override public boolean supportsParameter(MethodParameter parameter) { @@ -29,6 +29,6 @@ public Object resolveArgument( WebDataBinderFactory binderFactory ) { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); - return memberService.checkLogin(request.getCookies()); + return authService.authorizeByCookie(request.getCookies()); } } diff --git a/backend/src/main/java/codezap/member/configuration/AuthWebConfiguration.java b/backend/src/main/java/codezap/member/configuration/AuthWebConfiguration.java index c009b306f..e54e93d35 100644 --- a/backend/src/main/java/codezap/member/configuration/AuthWebConfiguration.java +++ b/backend/src/main/java/codezap/member/configuration/AuthWebConfiguration.java @@ -6,17 +6,17 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import codezap.member.service.MemberService; +import codezap.member.service.AuthService; import lombok.RequiredArgsConstructor; @Configuration @RequiredArgsConstructor public class AuthWebConfiguration implements WebMvcConfigurer { - private final MemberService memberService; + private final AuthService authService; @Override public void addArgumentResolvers(List resolvers) { - resolvers.add(new AuthArgumentResolver(memberService)); + resolvers.add(new AuthArgumentResolver(authService)); } } From 629e0b695d48c8bd75996c823796103f5c3c9ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98jminkkk=E2=80=99?= <102847513+jminkkk@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:26:29 +0900 Subject: [PATCH 080/168] =?UTF-8?q?fix(error):=20@ApiErrorResponse=20?= =?UTF-8?q?=EB=B0=98=EB=B3=B5=20=EC=A0=95=EC=9D=98=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/swagger/error/ApiErrorResponses.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 backend/src/main/java/codezap/global/swagger/error/ApiErrorResponses.java diff --git a/backend/src/main/java/codezap/global/swagger/error/ApiErrorResponses.java b/backend/src/main/java/codezap/global/swagger/error/ApiErrorResponses.java new file mode 100644 index 000000000..2589cf96d --- /dev/null +++ b/backend/src/main/java/codezap/global/swagger/error/ApiErrorResponses.java @@ -0,0 +1,12 @@ +package codezap.global.swagger.error; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiErrorResponses { + ApiErrorResponse[] value(); +} From ec7f58277efe690aca8b9145f9a19931f0d06f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98jminkkk=E2=80=99?= <102847513+jminkkk@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:27:36 +0900 Subject: [PATCH 081/168] =?UTF-8?q?fix(error):=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20@Repeatable=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codezap/global/swagger/error/ApiErrorResponse.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/java/codezap/global/swagger/error/ApiErrorResponse.java b/backend/src/main/java/codezap/global/swagger/error/ApiErrorResponse.java index 7af9ad4e4..30db818fa 100644 --- a/backend/src/main/java/codezap/global/swagger/error/ApiErrorResponse.java +++ b/backend/src/main/java/codezap/global/swagger/error/ApiErrorResponse.java @@ -2,6 +2,7 @@ import static java.lang.annotation.ElementType.METHOD; +import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -18,6 +19,7 @@ @Target(value = METHOD) @Retention(RetentionPolicy.RUNTIME) +@Repeatable(ApiErrorResponses.class) public @interface ApiErrorResponse { String type() default "about:blank"; From 8d6afea3e397cc0bacf06f6bc215f8c576f10b00 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 16:27:38 +0900 Subject: [PATCH 082/168] =?UTF-8?q?refactor(service):=20tag=EC=99=80=20tag?= =?UTF-8?q?Name=20=EB=B3=80=EC=88=98=EB=AA=85=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/codezap/template/service/TemplateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index c26a3346a..74b05b777 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -69,7 +69,7 @@ public Long create(CreateTemplateRequest createTemplateRequest) { private void createTags(CreateTemplateRequest createTemplateRequest, Template template) { templateTagRepository.saveAll(createTemplateRequest.tags().stream() - .map(tag -> tagRepository.save(new Tag(tag))) + .map(tagName -> tagRepository.save(new Tag(tagName))) .map(tag -> new TemplateTag(template, tag)) .toList() ); From 9ace4c4f5302a54c5c2499869d8d786038c9e110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Mon, 5 Aug 2024 16:27:28 +0900 Subject: [PATCH 083/168] =?UTF-8?q?refactor(member):=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC,=20=EC=82=AC=EC=9A=A9=EC=9E=90=EB=AA=85=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20api=EC=9D=98=20=EC=9D=91=EB=8B=B5=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/MemberController.java | 13 ++-- .../controller/SpringDocMemberController.java | 75 +++++++++++++++---- .../codezap/member/service/MemberService.java | 20 ++--- 3 files changed, 73 insertions(+), 35 deletions(-) diff --git a/backend/src/main/java/codezap/member/controller/MemberController.java b/backend/src/main/java/codezap/member/controller/MemberController.java index 430115838..393297b3d 100644 --- a/backend/src/main/java/codezap/member/controller/MemberController.java +++ b/backend/src/main/java/codezap/member/controller/MemberController.java @@ -9,7 +9,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseCookie; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -36,15 +35,15 @@ public void signup(@Valid @RequestBody SignupRequest request) { } @GetMapping("/check-email") - public ResponseEntity checkUniqueEmail(@RequestParam String email) { - boolean isUnique = memberService.isUniqueEmail(email); - return ResponseEntity.ok(isUnique); + @ResponseStatus(HttpStatus.OK) + public void checkUniqueEmail(@RequestParam String email) { + memberService.assertUniqueEmail(email); } @GetMapping("/check-username") - public ResponseEntity checkUniqueUsername(@RequestParam String username) { - boolean isUnique = memberService.isUniqueUsername(username); - return ResponseEntity.ok(isUnique); + @ResponseStatus(HttpStatus.OK) + public void checkUniqueUsername(@RequestParam String username) { + memberService.assertUniqueUsername(username); } @PostMapping("/login") diff --git a/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java b/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java index 8c1be6dd7..8d5f4a06a 100644 --- a/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java +++ b/backend/src/main/java/codezap/member/controller/SpringDocMemberController.java @@ -3,13 +3,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; -import codezap.global.swagger.error.ApiErrorResponse; -import codezap.global.swagger.error.ErrorCase; import codezap.global.swagger.error.ProblemDetailSchema; import codezap.member.dto.LoginRequest; import codezap.member.dto.SignupRequest; @@ -150,12 +146,50 @@ public interface SpringDocMemberController { void signup(@RequestBody SignupRequest request); @Operation(summary = "이메일 중복 확인") - @ApiResponse(responseCode = "200", description = "사용가능한 이메일이면 true, 중복된 이메일이면 false를 반환") - ResponseEntity checkUniqueEmail(@RequestParam String email); + @ApiResponse(responseCode = "200", description = "사용가능한 이메일") + @ApiResponse( + responseCode = "409", + description = "중복된 이메일", + content = @Content( + schema = @Schema(implementation = ProblemDetailSchema.class), + examples = { + @ExampleObject(name = "이메일 중복", value = """ + { + "type": "about:blank", + "title": "CONFLICT", + "status": 409, + "detail": "이메일이 이미 존재합니다.", + "instance": "/check-email" + } + """ + ) + } + ) + ) + void checkUniqueEmail(@RequestParam String email); @Operation(summary = "사용자명 중복 확인") - @ApiResponse(responseCode = "200", description = "사용가능한 사용자명이면 true, 중복된 사용자명이면 false를 반환") - ResponseEntity checkUniqueUsername(@RequestParam String username); + @ApiResponse(responseCode = "200", description = "사용가능한 사용자명") + @ApiResponse( + responseCode = "409", + description = "중복된 사용자명", + content = @Content( + schema = @Schema(implementation = ProblemDetailSchema.class), + examples = { + @ExampleObject(name = "사용자명 중복", value = """ + { + "type": "about:blank", + "title": "CONFLICT", + "status": 409, + "detail": "사용자명이 이미 존재합니다.", + "instance": "/check-username" + } + """ + ) + } + ) + ) + void checkUniqueUsername(@RequestParam String username); @Operation(summary = "이메일 로그인") @ApiResponse( @@ -277,20 +311,33 @@ public interface SpringDocMemberController { "title": "UNAUTHORIZED", "status": 401, "detail": "인증에 실패했습니다.", - "instance": "/login" + "instance": "/login/check" } """) } ) ) - @ApiErrorResponse( - status = HttpStatus.UNAUTHORIZED, - instance = "/login/check", - errorCases = {@ErrorCase(description = "쿠키 값 오류", exampleMessage = "인증에 실패했습니다.")} - ) void checkLogin(HttpServletRequest request); @Operation(summary = "로그아웃") @ApiResponse(responseCode = "204", description = "인증 성공") + @ApiResponse( + responseCode = "401", + description = "인증 실패", + content = @Content( + schema = @Schema(implementation = ProblemDetailSchema.class), + examples = { + @ExampleObject(name = "쿠키 값 오류", value = """ + { + "type": "about:blank", + "title": "UNAUTHORIZED", + "status": 401, + "detail": "인증에 실패했습니다.", + "instance": "/login/check" + } + """) + } + ) + ) void logout(HttpServletResponse response); } diff --git a/backend/src/main/java/codezap/member/service/MemberService.java b/backend/src/main/java/codezap/member/service/MemberService.java index b2e927e2e..a5f6b9696 100644 --- a/backend/src/main/java/codezap/member/service/MemberService.java +++ b/backend/src/main/java/codezap/member/service/MemberService.java @@ -31,27 +31,19 @@ public MemberDto login(LoginRequest request) { return authService.authorizeByEmailAndPassword(request.email(), request.password()); } - public MemberDto checkLogin(Cookie[] cookies) { - return authService.authorizeByCookie(cookies); + public void checkLogin(Cookie[] cookies) { + authService.authorizeByCookie(cookies); } - private void assertUniqueEmail(String email) { - if (!isUniqueEmail(email)) { + public void assertUniqueEmail(String email) { + if (!memberRepository.existsByEmail(email)) { throw new CodeZapException(HttpStatus.CONFLICT, "이메일이 이미 존재합니다."); } } - private void assertUniqueUsername(String username) { - if (!isUniqueUsername(username)) { + public void assertUniqueUsername(String username) { + if (!memberRepository.existsByUsername(username)) { throw new CodeZapException(HttpStatus.CONFLICT, "사용자명이 이미 존재합니다."); } } - - public boolean isUniqueEmail(String email) { - return !memberRepository.existsByEmail(email); - } - - public boolean isUniqueUsername(String username) { - return !memberRepository.existsByUsername(username); - } } From c117e0e96312f23fc706246407d91e0bf7c27fa1 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 16:34:38 +0900 Subject: [PATCH 084/168] =?UTF-8?q?refactor(service):=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template/service/TemplateService.java | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 74b05b777..f83819a24 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -75,6 +75,16 @@ private void createTags(CreateTemplateRequest createTemplateRequest, Template te ); } + private void createSnippet(CreateSnippetRequest createSnippetRequest, Template template) { + snippetRepository.save( + new Snippet( + template, createSnippetRequest.filename(), + createSnippetRequest.content(), + createSnippetRequest.ordinal() + ) + ); + } + public FindAllTemplatesResponse findAll() { return FindAllTemplatesResponse.from(thumbnailSnippetRepository.findAll()); } @@ -112,11 +122,32 @@ private void updateSnippets(UpdateTemplateRequest updateTemplateRequest, Templat updateTemplateRequest.deleteSnippetIds().forEach(snippetRepository::deleteById); } + private void updateSnippet(UpdateSnippetRequest updateSnippetRequest) { + Snippet snippet = snippetRepository.fetchById(updateSnippetRequest.id()); + snippet.updateSnippet(updateSnippetRequest.filename(), updateSnippetRequest.content(), + updateSnippetRequest.ordinal()); + } + + private static boolean isThumbnailSnippetDeleted( + UpdateTemplateRequest updateTemplateRequest, + ThumbnailSnippet thumbnailSnippet + ) { + return updateTemplateRequest.deleteSnippetIds().contains(thumbnailSnippet.getId()); + } + + private void updateThumbnailSnippet(Template template, ThumbnailSnippet thumbnailSnippet) { + List snippets = snippetRepository.findAllByTemplateAndOrdinal(template, FIRST_ORDINAL); + snippets.stream() + .filter(snippet -> !Objects.equals(thumbnailSnippet.getSnippet().getId(), snippet.getId())) + .findFirst() + .ifPresent(thumbnailSnippet::updateThumbnailSnippet); + } + private void updateTags(UpdateTemplateRequest updateTemplateRequest, Template template) { templateTagRepository.deleteAllByTemplateId(template.getId()); updateTemplateRequest.tags().stream() + .filter(tagName -> !tagRepository.existsByName(tagName)) .map(Tag::new) - .filter(tag -> !tagRepository.existsByName(tag.getName())) .forEach(tagRepository::save); List tags = updateTemplateRequest.tags().stream() @@ -139,35 +170,4 @@ public void deleteById(Long id) { templateTagRepository.deleteAllByTemplateId(id); templateRepository.deleteById(id); } - - private static boolean isThumbnailSnippetDeleted( - UpdateTemplateRequest updateTemplateRequest, - ThumbnailSnippet thumbnailSnippet - ) { - return updateTemplateRequest.deleteSnippetIds().contains(thumbnailSnippet.getId()); - } - - private void updateThumbnailSnippet(Template template, ThumbnailSnippet thumbnailSnippet) { - List snippets = snippetRepository.findAllByTemplateAndOrdinal(template, FIRST_ORDINAL); - snippets.stream() - .filter(snippet -> !Objects.equals(thumbnailSnippet.getSnippet().getId(), snippet.getId())) - .findFirst() - .ifPresent(thumbnailSnippet::updateThumbnailSnippet); - } - - private void createSnippet(CreateSnippetRequest createSnippetRequest, Template template) { - snippetRepository.save( - new Snippet( - template, createSnippetRequest.filename(), - createSnippetRequest.content(), - createSnippetRequest.ordinal() - ) - ); - } - - private void updateSnippet(UpdateSnippetRequest updateSnippetRequest) { - Snippet snippet = snippetRepository.fetchById(updateSnippetRequest.id()); - snippet.updateSnippet(updateSnippetRequest.filename(), updateSnippetRequest.content(), - updateSnippetRequest.ordinal()); - } } From eb14c4af07670cd96770b42d081e68ae8a2c9444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Mon, 5 Aug 2024 16:35:56 +0900 Subject: [PATCH 085/168] =?UTF-8?q?fix(service):=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/codezap/member/service/MemberService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/codezap/member/service/MemberService.java b/backend/src/main/java/codezap/member/service/MemberService.java index a5f6b9696..f90641b1d 100644 --- a/backend/src/main/java/codezap/member/service/MemberService.java +++ b/backend/src/main/java/codezap/member/service/MemberService.java @@ -36,13 +36,13 @@ public void checkLogin(Cookie[] cookies) { } public void assertUniqueEmail(String email) { - if (!memberRepository.existsByEmail(email)) { + if (memberRepository.existsByEmail(email)) { throw new CodeZapException(HttpStatus.CONFLICT, "이메일이 이미 존재합니다."); } } public void assertUniqueUsername(String username) { - if (!memberRepository.existsByUsername(username)) { + if (memberRepository.existsByUsername(username)) { throw new CodeZapException(HttpStatus.CONFLICT, "사용자명이 이미 존재합니다."); } } From 99b6fd931cb84fcf70aa65b97b9dc9d33794fcaf Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 16:37:24 +0900 Subject: [PATCH 086/168] =?UTF-8?q?refactor(service):=20=ED=83=9C=EA=B7=B8?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template/service/TemplateService.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index f83819a24..f9afeff94 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -68,11 +68,14 @@ public Long create(CreateTemplateRequest createTemplateRequest) { } private void createTags(CreateTemplateRequest createTemplateRequest, Template template) { - templateTagRepository.saveAll(createTemplateRequest.tags().stream() - .map(tagName -> tagRepository.save(new Tag(tagName))) - .map(tag -> new TemplateTag(template, tag)) - .toList() - ); + createTemplateRequest.tags().stream() + .filter(tagName -> !tagRepository.existsByName(tagName)) + .map(Tag::new) + .forEach(tagRepository::save); + + createTemplateRequest.tags().stream() + .map(tagRepository::findByName) + .forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); } private void createSnippet(CreateSnippetRequest createSnippetRequest, Template template) { @@ -150,10 +153,9 @@ private void updateTags(UpdateTemplateRequest updateTemplateRequest, Template te .map(Tag::new) .forEach(tagRepository::save); - List tags = updateTemplateRequest.tags().stream() + updateTemplateRequest.tags().stream() .map(tagRepository::findByName) - .toList(); - tags.forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); + .forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); } private void validateSnippetsCount(UpdateTemplateRequest updateTemplateRequest, Template template) { From 3ad771762aa33236a64f81af0a16f44f6edc1723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Mon, 5 Aug 2024 16:47:51 +0900 Subject: [PATCH 087/168] =?UTF-8?q?test(service):=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/FakeMemberRepository.java | 9 ++-- .../member/service/MemberServiceTest.java | 52 ++++++++++++++++++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java b/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java index a6c3728c4..1037f2bb2 100644 --- a/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java +++ b/backend/src/test/java/codezap/member/repository/FakeMemberRepository.java @@ -20,14 +20,12 @@ public FakeMemberRepository() { @Override public boolean existsByEmail(String email) { - return members.stream() - .anyMatch(member -> Objects.equals(member.getEmail(), email)); + return members.stream().anyMatch(member -> Objects.equals(member.getEmail(), email)); } @Override public boolean existsByUsername(String username) { - return members.stream() - .anyMatch(member -> Objects.equals(member.getUsername(), username)); + return members.stream().anyMatch(member -> Objects.equals(member.getUsername(), username)); } @Override @@ -58,7 +56,6 @@ private long getOrGenerateId(Member entity) { } private boolean existsById(Long id) { - return members.stream() - .anyMatch(member -> Objects.equals(member.getId(), id)); + return members.stream().anyMatch(member -> Objects.equals(member.getId(), id)); } } diff --git a/backend/src/test/java/codezap/member/service/MemberServiceTest.java b/backend/src/test/java/codezap/member/service/MemberServiceTest.java index cb2f41cdb..96dd0b135 100644 --- a/backend/src/test/java/codezap/member/service/MemberServiceTest.java +++ b/backend/src/test/java/codezap/member/service/MemberServiceTest.java @@ -15,11 +15,11 @@ import codezap.global.exception.CodeZapException; import codezap.member.domain.Member; -import codezap.member.repository.MemberRepository; import codezap.member.dto.LoginRequest; import codezap.member.dto.MemberDto; import codezap.member.dto.SignupRequest; import codezap.member.repository.FakeMemberRepository; +import codezap.member.repository.MemberRepository; public class MemberServiceTest { @@ -27,6 +27,56 @@ public class MemberServiceTest { private final AuthService authService = new AuthService(memberRepository); private final MemberService sut = new MemberService(memberRepository, authService); + @Nested + @DisplayName("이메일 중복 검사 테스트") + class CheckEmail { + + @Test + @DisplayName("이메일 중복 검사 통과: 사용가능한 이메일") + void assertUniqueEmail() { + var email = "code@zap.com"; + + assertThatCode(() -> sut.assertUniqueEmail(email)) + .doesNotThrowAnyException(); + } + + @Test + @DisplayName("이메일 중복 검사 실패: 중복된 이메일") + void assertUniqueEmail_fail_duplicate() { + var savedMember = new Member(1L, "code@zap.com", "password", "zappy"); + memberRepository.save(savedMember); + + assertThatThrownBy(() -> sut.assertUniqueEmail("code@zap.com")) + .isInstanceOf(CodeZapException.class) + .hasMessage("이메일이 이미 존재합니다."); + } + } + + @Nested + @DisplayName("사용자명 중복 검사 테스트") + class CheckUsername { + + @Test + @DisplayName("사용자명 중복 검사 통과: 사용가능한 사용자명") + void assertUniqueUsername() { + var username = "zappy"; + + assertThatCode(() -> sut.assertUniqueUsername(username)) + .doesNotThrowAnyException(); + } + + @Test + @DisplayName("사용자명 중복 검사 실패: 중복된 사용자명") + void assertUniqueUsername_fail_duplicate() { + var savedMember = new Member(1L, "code@zap.com", "password", "zappy"); + memberRepository.save(savedMember); + + assertThatThrownBy(() -> sut.assertUniqueUsername("zappy")) + .isInstanceOf(CodeZapException.class) + .hasMessage("사용자명이 이미 존재합니다."); + } + } + @Nested @DisplayName("회원가입 테스트") class SignupTest { From 80a57d02fe63fd652e7dcafa3d2fac8a221b87b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98jminkkk=E2=80=99?= <102847513+jminkkk@users.noreply.github.com> Date: Mon, 5 Aug 2024 21:17:08 +0900 Subject: [PATCH 088/168] =?UTF-8?q?feat(logger):=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EB=A1=9C=EA=B9=85=20=EC=8B=9C=20=EC=BF=A0=ED=82=A4=20=EA=B0=92?= =?UTF-8?q?=EB=8F=84=20=ED=95=A8=EA=BB=98=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codezap/global/logger/RequestResponseLogger.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/global/logger/RequestResponseLogger.java b/backend/src/main/java/codezap/global/logger/RequestResponseLogger.java index fd400536d..38479cdf1 100644 --- a/backend/src/main/java/codezap/global/logger/RequestResponseLogger.java +++ b/backend/src/main/java/codezap/global/logger/RequestResponseLogger.java @@ -24,6 +24,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse throws ServletException, IOException { ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request); ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response); + String cookieHeader = requestWrapper.getHeader("Cookie"); long startTime = System.currentTimeMillis(); filterChain.doFilter(requestWrapper, responseWrapper); @@ -32,7 +33,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String requestBody = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8); String responseBody = new String(responseWrapper.getContentAsByteArray(), StandardCharsets.UTF_8); - log.info("[Request] {}, {}, 요청 바디: {}", request.getMethod(), request.getRequestURI(), requestBody); + log.info("[Request] {} {}, 쿠키 헤더 값: {} \n 요청 바디: {}", request.getMethod(), request.getRequestURI(), + cookieHeader, requestBody); log.info("[Response] Status: {}, Duration: {}ms, 응답 바디: {}", response.getStatus(), duration, responseBody); responseWrapper.copyBodyToResponse(); From d7c748132d357df2ff4fb6faa60caa4a6fe07beb Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 21:20:37 +0900 Subject: [PATCH 089/168] =?UTF-8?q?refactor(controller):=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EA=B0=9C=ED=96=89=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codezap/category/controller/CategoryController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/main/java/codezap/category/controller/CategoryController.java b/backend/src/main/java/codezap/category/controller/CategoryController.java index 0ca9511bc..7ef8c0788 100644 --- a/backend/src/main/java/codezap/category/controller/CategoryController.java +++ b/backend/src/main/java/codezap/category/controller/CategoryController.java @@ -55,7 +55,6 @@ public ResponseEntity updateCategory( @DeleteMapping("/{id}") public ResponseEntity deleteCategory(@PathVariable Long id) { categoryService.deleteById(id); - return ResponseEntity.noContent() - .build(); + return ResponseEntity.noContent().build(); } } From d6bcbce327860d6bb145cbf95bad85cb2d446b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98jminkkk=E2=80=99?= <102847513+jminkkk@users.noreply.github.com> Date: Mon, 5 Aug 2024 21:24:42 +0900 Subject: [PATCH 090/168] =?UTF-8?q?refactor(logger):=20Content=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B8=EC=BD=94=EB=94=A9=ED=95=98=EB=8A=94=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=BD=94=EB=93=9C=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/global/logger/RequestResponseLogger.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/codezap/global/logger/RequestResponseLogger.java b/backend/src/main/java/codezap/global/logger/RequestResponseLogger.java index 38479cdf1..8562b1327 100644 --- a/backend/src/main/java/codezap/global/logger/RequestResponseLogger.java +++ b/backend/src/main/java/codezap/global/logger/RequestResponseLogger.java @@ -30,16 +30,18 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse filterChain.doFilter(requestWrapper, responseWrapper); long duration = System.currentTimeMillis() - startTime; - String requestBody = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8); - String responseBody = new String(responseWrapper.getContentAsByteArray(), StandardCharsets.UTF_8); - log.info("[Request] {} {}, 쿠키 헤더 값: {} \n 요청 바디: {}", request.getMethod(), request.getRequestURI(), - cookieHeader, requestBody); - log.info("[Response] Status: {}, Duration: {}ms, 응답 바디: {}", response.getStatus(), duration, responseBody); + cookieHeader, getBodyAsUtf8String(requestWrapper.getContentAsByteArray())); + log.info("[Response] Status: {}, Duration: {}ms, 응답 바디: {}", response.getStatus(), duration, + getBodyAsUtf8String(responseWrapper.getContentAsByteArray())); responseWrapper.copyBodyToResponse(); } + private String getBodyAsUtf8String(byte[] bytes) { + return new String(bytes, StandardCharsets.UTF_8); + } + @Override protected boolean shouldNotFilter(HttpServletRequest request) { String path = request.getRequestURI(); From eac479c44f9ad4eec5acddf1ddf273266645a000 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 21:27:35 +0900 Subject: [PATCH 091/168] =?UTF-8?q?refactor(service):=20=EC=83=81=EC=88=98?= =?UTF-8?q?=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=EC=96=B4=EC=9E=90=20private?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/codezap/category/service/CategoryService.java | 2 +- .../src/main/java/codezap/template/service/TemplateService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index 309d5e17b..ce934712b 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -15,7 +15,7 @@ @Service public class CategoryService { - public static final int DEFAULT_CATEGORY = 1; + private static final long DEFAULT_CATEGORY = 1L; private final CategoryRepository categoryRepository; private final TemplateRepository templateRepository; diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index f9afeff94..601f7e690 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -30,7 +30,7 @@ @Service public class TemplateService { - public static final int FIRST_ORDINAL = 1; + private static final int FIRST_ORDINAL = 1; private final ThumbnailSnippetRepository thumbnailSnippetRepository; private final TemplateRepository templateRepository; From 34c94c852f89042553bd5a0de4fa310214773c1d Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 21:28:59 +0900 Subject: [PATCH 092/168] =?UTF-8?q?refactor(domain):=20=EC=83=81=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/codezap/template/domain/Snippet.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/codezap/template/domain/Snippet.java b/backend/src/main/java/codezap/template/domain/Snippet.java index a70a74e77..31974afe8 100644 --- a/backend/src/main/java/codezap/template/domain/Snippet.java +++ b/backend/src/main/java/codezap/template/domain/Snippet.java @@ -21,7 +21,7 @@ @Getter public class Snippet extends BaseTimeEntity { - private static final String CODE_LINE_BREAK = "\n"; + private static final String LINE_BREAK = "\n"; private static final int THUMBNAIL_SNIPPET_LINE_HEIGHT = 10; @Id @@ -48,9 +48,9 @@ public Snippet(Template template, String filename, String content, Integer ordin } public String getThumbnailContent() { - return Arrays.stream(content.split(CODE_LINE_BREAK)) + return Arrays.stream(content.split(LINE_BREAK)) .limit(THUMBNAIL_SNIPPET_LINE_HEIGHT) - .collect(Collectors.joining(CODE_LINE_BREAK)); + .collect(Collectors.joining(LINE_BREAK)); } public void updateSnippet(String filename, String content, Integer ordinal) { From 4cbfb71bec47718dca8122ac3b97b6afeb6bafbc Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 21:30:32 +0900 Subject: [PATCH 093/168] =?UTF-8?q?refactor(response):=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EA=B0=9C=ED=96=89=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/template/dto/response/FindTemplateResponse.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/main/java/codezap/template/dto/response/FindTemplateResponse.java b/backend/src/main/java/codezap/template/dto/response/FindTemplateResponse.java index 3920cfdd8..42fe1f294 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindTemplateResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindTemplateResponse.java @@ -51,9 +51,7 @@ private static List mapToFindAllSnippetByTempl .toList(); } - private static List mapToFindTagByTemplateResponse( - List tags - ) { + private static List mapToFindTagByTemplateResponse(List tags) { return tags.stream() .map(FindTagResponse::from) .toList(); From 8b1b520fa47e07f757b431bca37a95ceb9f3db74 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 21:36:46 +0900 Subject: [PATCH 094/168] =?UTF-8?q?refactor(codezap):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=83=9D=EC=84=B1=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template/controller/TemplateController.java | 3 ++- .../java/codezap/template/service/TemplateService.java | 2 +- .../category/controller/CategoryControllerTest.java | 2 +- .../template/controller/TemplateControllerTest.java | 10 +++++----- .../codezap/template/service/TemplateServiceTest.java | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/codezap/template/controller/TemplateController.java b/backend/src/main/java/codezap/template/controller/TemplateController.java index 5a4cec80a..15298c9b5 100644 --- a/backend/src/main/java/codezap/template/controller/TemplateController.java +++ b/backend/src/main/java/codezap/template/controller/TemplateController.java @@ -33,7 +33,8 @@ public TemplateController(TemplateService templateService) { public ResponseEntity create( @Validated(ValidationSequence.class) @RequestBody CreateTemplateRequest createTemplateRequest ) { - return ResponseEntity.created(URI.create("/templates/" + templateService.create(createTemplateRequest))) + Long createdTemplateId = templateService.createTemplate(createTemplateRequest); + return ResponseEntity.created(URI.create("/templates/" + createdTemplateId)) .build(); } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 601f7e690..e29370d45 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -53,7 +53,7 @@ public TemplateService(ThumbnailSnippetRepository thumbnailSnippetRepository, } @Transactional - public Long create(CreateTemplateRequest createTemplateRequest) { + public Long createTemplate(CreateTemplateRequest createTemplateRequest) { Category category = categoryRepository.fetchById(createTemplateRequest.categoryId()); Template template = templateRepository.save( new Template(createTemplateRequest.title(), createTemplateRequest.description(), category) diff --git a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java index e99ed71c3..224efb7bc 100644 --- a/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java +++ b/backend/src/test/java/codezap/category/controller/CategoryControllerTest.java @@ -185,7 +185,7 @@ void updateCategoryFailWithDuplicatedName() { @DisplayName("카테고리 삭제 실패: 템플릿이 존재하는 카테고리는 삭제 불가능") void updateCategoryFailWithLongName() { // given - templateService.create(new CreateTemplateRequest( + templateService.createTemplate(new CreateTemplateRequest( "title", "description", List.of(new CreateSnippetRequest("filename", "content", 1)), diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 9641ff454..217695f9d 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -194,8 +194,8 @@ void findAllTemplatesSuccess() { categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest1 = createTemplateRequestWithTwoSnippets("title1"); CreateTemplateRequest templateRequest2 = createTemplateRequestWithTwoSnippets("title2"); - templateService.create(templateRequest1); - templateService.create(templateRequest2); + templateService.createTemplate(templateRequest1); + templateService.createTemplate(templateRequest2); // when & then RestAssured.given().log().all() @@ -215,7 +215,7 @@ void findOneTemplateSuccess() { // given categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); - templateService.create(templateRequest); + templateService.createTemplate(templateRequest); // when & then RestAssured.given().log().all() @@ -434,7 +434,7 @@ private void createTemplateAndTwoCategories() { categoryService.create(new CreateCategoryRequest("category1")); categoryService.create(new CreateCategoryRequest("category2")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); - templateService.create(templateRequest); + templateService.createTemplate(templateRequest); } } @@ -448,7 +448,7 @@ void deleteTemplateSuccess() { // given categoryService.create(new CreateCategoryRequest("category")); CreateTemplateRequest templateRequest = createTemplateRequestWithTwoSnippets("title"); - templateService.create(templateRequest); + templateService.createTemplate(templateRequest); // when & then RestAssured.given().log().all() diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 728e046b5..801ca13b4 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -76,7 +76,7 @@ void createTemplateSuccess() { CreateTemplateRequest createTemplateRequest = makeTemplateRequest("title"); // when - Long id = templateService.create(createTemplateRequest); + Long id = templateService.createTemplate(createTemplateRequest); Template template = templateRepository.fetchById(id); // then From 39336abd30a5de74675a4174aa611be8df67b2fc Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 21:46:17 +0900 Subject: [PATCH 095/168] =?UTF-8?q?refactor(service):=20stream=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20save=20=EB=8C=80=EC=8B=A0=20saveAll?= =?UTF-8?q?=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template/service/TemplateService.java | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index e29370d45..154cd76fb 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -59,8 +59,11 @@ public Long createTemplate(CreateTemplateRequest createTemplateRequest) { new Template(createTemplateRequest.title(), createTemplateRequest.description(), category) ); createTags(createTemplateRequest, template); - createTemplateRequest.snippets() - .forEach(createSnippetRequest -> createSnippet(createSnippetRequest, template)); + snippetRepository.saveAll( + createTemplateRequest.snippets().stream() + .map(createSnippetRequest -> createSnippet(createSnippetRequest, template)) + .toList() + ); Snippet thumbnailSnippet = snippetRepository.findByTemplateAndOrdinal(template, FIRST_ORDINAL); thumbnailSnippetRepository.save(new ThumbnailSnippet(template, thumbnailSnippet)); @@ -68,23 +71,26 @@ public Long createTemplate(CreateTemplateRequest createTemplateRequest) { } private void createTags(CreateTemplateRequest createTemplateRequest, Template template) { - createTemplateRequest.tags().stream() + tagRepository.saveAll( + createTemplateRequest.tags().stream() .filter(tagName -> !tagRepository.existsByName(tagName)) .map(Tag::new) - .forEach(tagRepository::save); + .toList() + ); - createTemplateRequest.tags().stream() + templateTagRepository.saveAll( + createTemplateRequest.tags().stream() .map(tagRepository::findByName) - .forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); + .map(tag -> new TemplateTag(template, tag)) + .toList() + ); } - private void createSnippet(CreateSnippetRequest createSnippetRequest, Template template) { - snippetRepository.save( - new Snippet( - template, createSnippetRequest.filename(), - createSnippetRequest.content(), - createSnippetRequest.ordinal() - ) + private Snippet createSnippet(CreateSnippetRequest createSnippetRequest, Template template) { + return new Snippet( + template, createSnippetRequest.filename(), + createSnippetRequest.content(), + createSnippetRequest.ordinal() ); } @@ -148,14 +154,19 @@ private void updateThumbnailSnippet(Template template, ThumbnailSnippet thumbnai private void updateTags(UpdateTemplateRequest updateTemplateRequest, Template template) { templateTagRepository.deleteAllByTemplateId(template.getId()); - updateTemplateRequest.tags().stream() + tagRepository.saveAll( + updateTemplateRequest.tags().stream() .filter(tagName -> !tagRepository.existsByName(tagName)) .map(Tag::new) - .forEach(tagRepository::save); + .toList() + ); - updateTemplateRequest.tags().stream() + templateTagRepository.saveAll( + updateTemplateRequest.tags().stream() .map(tagRepository::findByName) - .forEach(tag -> templateTagRepository.save(new TemplateTag(template, tag))); + .map(tag -> new TemplateTag(template, tag)) + .toList() + ); } private void validateSnippetsCount(UpdateTemplateRequest updateTemplateRequest, Template template) { From 72d3fa211e4ab14ee60c263d92484cc04c462cb1 Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Wed, 24 Jul 2024 16:20:51 +0900 Subject: [PATCH 096/168] =?UTF-8?q?docs:=20=ED=85=9C=ED=94=8C=EB=A6=BF=20?= =?UTF-8?q?=ED=86=A0=ED=94=BD=20=EA=B2=80=EC=83=89=20API=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template/controller/SpringDocTemplateController.java | 5 +++++ .../codezap/template/controller/TemplateController.java | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java index cd7493a51..cca4282b6 100644 --- a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java +++ b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java @@ -51,6 +51,11 @@ public interface SpringDocTemplateController { }) ResponseEntity getTemplateById(Long id); + @Operation(summary = "템플릿 토픽 검색", description = "토픽이 포함된 템플릿들을 검색합니다.") + @ApiResponse(responseCode = "200", description = "템플릿 토픽 검색 성공", + content = {@Content(schema = @Schema(implementation = FindAllTemplatesResponse.class))}) + ResponseEntity getTemplatesContainTopic(String topic); + @Operation(summary = "템플릿 수정", description = "해당하는 식별자의 템플릿을 수정합니다.") @ApiResponse(responseCode = "200", description = "템플릿 수정 성공") @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/templates/1", errorCases = { diff --git a/backend/src/main/java/codezap/template/controller/TemplateController.java b/backend/src/main/java/codezap/template/controller/TemplateController.java index 5a4cec80a..6464fea73 100644 --- a/backend/src/main/java/codezap/template/controller/TemplateController.java +++ b/backend/src/main/java/codezap/template/controller/TemplateController.java @@ -2,6 +2,8 @@ import java.net.URI; +import org.apache.commons.lang3.NotImplementedException; +import org.springframework.data.repository.query.Param; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -47,6 +49,11 @@ public ResponseEntity getTemplateById(@PathVariable Long i return ResponseEntity.ok(templateService.findById(id)); } + @GetMapping("/search") + public ResponseEntity getTemplatesContainTopic(@Param("topic") String topic) { + throw new NotImplementedException(); + } + @PostMapping("/{id}") public ResponseEntity updateTemplate( @PathVariable Long id, From 1ad7b14ed2676cc0beb860c22cccecdb6bba2918 Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:23:04 +0900 Subject: [PATCH 097/168] =?UTF-8?q?feat(service):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=A0=9C=EB=AA=A9=EC=97=90=20=ED=86=A0=ED=94=BD?= =?UTF-8?q?=EC=9D=B4=20=ED=8F=AC=ED=95=A8=EB=90=9C=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EA=B2=B0=EA=B3=BC=EC=97=90=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ThumbnailSnippetRepository.java | 12 ++++++++++++ .../template/service/TemplateService.java | 4 ++++ .../template/service/TemplateServiceTest.java | 16 ++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java b/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java index bb836ed6e..0ddf5f727 100644 --- a/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java +++ b/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java @@ -1,12 +1,24 @@ package codezap.template.repository; +import java.util.List; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import codezap.template.domain.Template; import codezap.template.domain.ThumbnailSnippet; public interface ThumbnailSnippetRepository extends JpaRepository { + ThumbnailSnippet findByTemplate(Template template); void deleteByTemplateId(Long id); + + @Query(""" + SELECT t + FROM ThumbnailSnippet t + WHERE t.template.title LIKE %:topic% + """) + List searchByTopic(@Param("topic") String topic); } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index f9afeff94..520401551 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -172,4 +172,8 @@ public void deleteById(Long id) { templateTagRepository.deleteAllByTemplateId(id); templateRepository.deleteById(id); } + + public FindAllTemplatesResponse findContainTopic(String topic) { + return FindAllTemplatesResponse.from(thumbnailSnippetRepository.searchByTopic(topic)); + } } diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 728e046b5..7b75280c4 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -168,6 +168,22 @@ void deleteTemplateSuccess() { ); } + @Test + @DisplayName("템플릿 토픽 검색 성공 : 템플릿 제목에 포함") + void findAllTemplatesTitleContainTopicSuccess() { + //given + saveTemplate(makeTemplateRequest("hello")); + saveTemplate(makeTemplateRequest("hello topic")); + saveTemplate(makeTemplateRequest("topic hello")); + saveTemplate(makeTemplateRequest("hello topic !")); + + //when + FindAllTemplatesResponse templates = templateService.findContainTopic("topic"); + + //then + assertThat(templates.templates()).hasSize(3); + } + private CreateTemplateRequest makeTemplateRequest(String title) { return new CreateTemplateRequest( title, From 733a2c8c03d33daac7298b14c47e23f8eb6eedb4 Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:56:03 +0900 Subject: [PATCH 098/168] =?UTF-8?q?feat(service):=20=ED=83=AC=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=EC=9D=98=20=EC=8A=A4=EB=8B=88=ED=8E=AB=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=AA=85=20=EC=A4=91=20=ED=95=98=EB=82=98=EB=9D=BC?= =?UTF-8?q?=EB=8F=84=20=ED=86=A0=ED=94=BD=EC=9D=B4=20=ED=8F=AC=ED=95=A8?= =?UTF-8?q?=EB=90=9C=20=EA=B2=BD=EC=9A=B0=20=EA=B2=80=EC=83=89=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EC=97=90=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ThumbnailSnippetRepository.java | 3 +- .../template/service/TemplateServiceTest.java | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java b/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java index 0ddf5f727..97754e833 100644 --- a/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java +++ b/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java @@ -17,8 +17,9 @@ public interface ThumbnailSnippetRepository extends JpaRepository searchByTopic(@Param("topic") String topic); } diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 7b75280c4..6d00112a3 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -184,6 +184,21 @@ void findAllTemplatesTitleContainTopicSuccess() { assertThat(templates.templates()).hasSize(3); } + @Test + @DisplayName("템플릿 토픽 검색 성공 : 탬플릿 내에 스니펫 파일명 중 하나라도 포함") + void findAllSnippetFilenameContainTopicSuccess() { + //given + saveTemplateBySnippetFilename("tempate1", "login.js", "signup.js"); + saveTemplateBySnippetFilename("tempate2", "login.java", "signup.java"); + saveTemplateBySnippetFilename("tempate3", "login.js", "signup.java"); + + //when + FindAllTemplatesResponse templates = templateService.findContainTopic("java"); + + //then + assertThat(templates.templates()).hasSize(2); + } + private CreateTemplateRequest makeTemplateRequest(String title) { return new CreateTemplateRequest( title, @@ -233,4 +248,19 @@ private Template saveTemplate(CreateTemplateRequest createTemplateRequest) { return savedTemplate; } + + private void saveTemplateBySnippetFilename(String templateTitle, String firstFilename, String secondFilename) { + CreateTemplateRequest createTemplateRequest = new CreateTemplateRequest( + templateTitle, + List.of( + new CreateSnippetRequest(firstFilename, "content1", 1), + new CreateSnippetRequest(secondFilename, "content2", 2) + ) + ); + Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title())); + + Snippet savedFirstSnippet = snippetRepository.save(new Snippet(savedTemplate, firstFilename, "content1", 1)); + snippetRepository.save(new Snippet(savedTemplate, secondFilename, "content2", 2)); + thumbnailSnippetRepository.save(new ThumbnailSnippet(savedTemplate, savedFirstSnippet)); + } } From 9a045ff8655cadb0b9dbd915f8376608a575f45c Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:05:53 +0900 Subject: [PATCH 099/168] =?UTF-8?q?feat(service):=20=ED=83=AC=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=EC=9D=98=20=EC=8A=A4=EB=8B=88=ED=8E=AB=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A4=91=20=ED=95=98=EB=82=98=EB=9D=BC=EB=8F=84=20?= =?UTF-8?q?=ED=86=A0=ED=94=BD=EC=9D=B4=20=ED=8F=AC=ED=95=A8=EB=90=9C=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=EA=B2=80=EC=83=89=20=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=EC=97=90=20=ED=8F=AC=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ThumbnailSnippetRepository.java | 1 + .../template/service/TemplateServiceTest.java | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java b/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java index 97754e833..5ec6ad782 100644 --- a/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java +++ b/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java @@ -20,6 +20,7 @@ public interface ThumbnailSnippetRepository extends JpaRepository searchByTopic(@Param("topic") String topic); } diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 6d00112a3..11ff15c79 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -199,6 +199,21 @@ void findAllSnippetFilenameContainTopicSuccess() { assertThat(templates.templates()).hasSize(2); } + @Test + @DisplayName("템플릿 토픽 검색 성공 : 탬플릿 내에 스니펫 코드 중 하나라도 포함") + void findAllSnippetContentContainTopicSuccess() { + //given + saveTemplateBySnippetContent("tempate1", "public Main {", "new Car();"); + saveTemplateBySnippetContent("tempate2", "private Car", "public Movement"); + saveTemplateBySnippetContent("tempate3", "console.log", "a+b=3"); + + //when + FindAllTemplatesResponse templates = templateService.findContainTopic("Car"); + + //then + assertThat(templates.templates()).hasSize(2); + } + private CreateTemplateRequest makeTemplateRequest(String title) { return new CreateTemplateRequest( title, @@ -263,4 +278,19 @@ private void saveTemplateBySnippetFilename(String templateTitle, String firstFil snippetRepository.save(new Snippet(savedTemplate, secondFilename, "content2", 2)); thumbnailSnippetRepository.save(new ThumbnailSnippet(savedTemplate, savedFirstSnippet)); } + + private void saveTemplateBySnippetContent(String templateTitle, String firstContent, String secondContent) { + CreateTemplateRequest createTemplateRequest = new CreateTemplateRequest( + templateTitle, + List.of( + new CreateSnippetRequest("filename1", firstContent, 1), + new CreateSnippetRequest("filename2", secondContent, 2) + ) + ); + Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title())); + + Snippet savedFirstSnippet = snippetRepository.save(new Snippet(savedTemplate, "filename1", firstContent, 1)); + snippetRepository.save(new Snippet(savedTemplate, "filename2", secondContent, 2)); + thumbnailSnippetRepository.save(new ThumbnailSnippet(savedTemplate, savedFirstSnippet)); + } } From aece2caf640f8b99da4dc0cb19decf7bc7e31c10 Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:18:58 +0900 Subject: [PATCH 100/168] =?UTF-8?q?feat(controller):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=ED=86=A0=ED=94=BD=20=EA=B2=80=EC=83=89=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TemplateController.java | 6 +-- .../controller/TemplateControllerTest.java | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/codezap/template/controller/TemplateController.java b/backend/src/main/java/codezap/template/controller/TemplateController.java index 6464fea73..811f68798 100644 --- a/backend/src/main/java/codezap/template/controller/TemplateController.java +++ b/backend/src/main/java/codezap/template/controller/TemplateController.java @@ -3,7 +3,6 @@ import java.net.URI; import org.apache.commons.lang3.NotImplementedException; -import org.springframework.data.repository.query.Param; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -12,6 +11,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import codezap.global.validation.ValidationSequence; @@ -50,8 +50,8 @@ public ResponseEntity getTemplateById(@PathVariable Long i } @GetMapping("/search") - public ResponseEntity getTemplatesContainTopic(@Param("topic") String topic) { - throw new NotImplementedException(); + public ResponseEntity getTemplatesContainTopic(@RequestParam String topic) { + return ResponseEntity.ok(templateService.findContainTopic(topic)); } @PostMapping("/{id}") diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 9641ff454..026169e82 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -482,4 +482,42 @@ private static CreateTemplateRequest createTemplateRequestWithTwoSnippets(String ); return templateRequest; } + + @Test + @DisplayName("템플릿 토픽 검색 성공") + void searchTopicSuccess() { + //given + String topic = "java"; + + CreateTemplateRequest templateRequest1 = new CreateTemplateRequest(topic, + List.of(new CreateSnippetRequest("filename", "content", 1))); + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(templateRequest1) + .when().post("/templates") + .then().log().all(); + + CreateTemplateRequest templateRequest2 = new CreateTemplateRequest("title2", + List.of(new CreateSnippetRequest("filename." + topic, "content", 1))); + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(templateRequest2) + .when().post("/templates") + .then().log().all(); + + CreateTemplateRequest templateRequest3 = new CreateTemplateRequest("title3", + List.of(new CreateSnippetRequest("filename", "// " + topic + " text", 1))); + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(templateRequest3) + .when().post("/templates") + .then().log().all(); + + //when + RestAssured.given().log().all() + .get("/templates/search?topic=" + topic) + .then().log().all() + .statusCode(200) + .body("templates.size()", is(3)); + } } From c069d1215eddd1a1097ec1a34439554daeaaab6b Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:22:07 +0900 Subject: [PATCH 101/168] =?UTF-8?q?test(service):=20nested=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=9C=20=EA=B2=80=EC=83=89=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template/service/TemplateServiceTest.java | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 11ff15c79..bc91a71d9 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -168,50 +169,54 @@ void deleteTemplateSuccess() { ); } - @Test - @DisplayName("템플릿 토픽 검색 성공 : 템플릿 제목에 포함") - void findAllTemplatesTitleContainTopicSuccess() { - //given - saveTemplate(makeTemplateRequest("hello")); - saveTemplate(makeTemplateRequest("hello topic")); - saveTemplate(makeTemplateRequest("topic hello")); - saveTemplate(makeTemplateRequest("hello topic !")); - - //when - FindAllTemplatesResponse templates = templateService.findContainTopic("topic"); - - //then - assertThat(templates.templates()).hasSize(3); - } - - @Test - @DisplayName("템플릿 토픽 검색 성공 : 탬플릿 내에 스니펫 파일명 중 하나라도 포함") - void findAllSnippetFilenameContainTopicSuccess() { - //given - saveTemplateBySnippetFilename("tempate1", "login.js", "signup.js"); - saveTemplateBySnippetFilename("tempate2", "login.java", "signup.java"); - saveTemplateBySnippetFilename("tempate3", "login.js", "signup.java"); - - //when - FindAllTemplatesResponse templates = templateService.findContainTopic("java"); - - //then - assertThat(templates.templates()).hasSize(2); - } - - @Test - @DisplayName("템플릿 토픽 검색 성공 : 탬플릿 내에 스니펫 코드 중 하나라도 포함") - void findAllSnippetContentContainTopicSuccess() { - //given - saveTemplateBySnippetContent("tempate1", "public Main {", "new Car();"); - saveTemplateBySnippetContent("tempate2", "private Car", "public Movement"); - saveTemplateBySnippetContent("tempate3", "console.log", "a+b=3"); - - //when - FindAllTemplatesResponse templates = templateService.findContainTopic("Car"); - - //then - assertThat(templates.templates()).hasSize(2); + @Nested + @DisplayName("템플릿 토픽 검색") + class searchContainTopic { + @Test + @DisplayName("성공 : 템플릿 제목에 포함") + void findAllTemplatesTitleContainTopicSuccess() { + //given + saveTemplate(makeTemplateRequest("hello")); + saveTemplate(makeTemplateRequest("hello topic")); + saveTemplate(makeTemplateRequest("topic hello")); + saveTemplate(makeTemplateRequest("hello topic !")); + + //when + FindAllTemplatesResponse templates = templateService.findContainTopic("topic"); + + //then + assertThat(templates.templates()).hasSize(3); + } + + @Test + @DisplayName("성공 : 탬플릿 내에 스니펫 파일명 중 하나라도 포함") + void findAllSnippetFilenameContainTopicSuccess() { + //given + saveTemplateBySnippetFilename("tempate1", "login.js", "signup.js"); + saveTemplateBySnippetFilename("tempate2", "login.java", "signup.java"); + saveTemplateBySnippetFilename("tempate3", "login.js", "signup.java"); + + //when + FindAllTemplatesResponse templates = templateService.findContainTopic("java"); + + //then + assertThat(templates.templates()).hasSize(2); + } + + @Test + @DisplayName("성공 : 탬플릿 내에 스니펫 코드 중 하나라도 포함") + void findAllSnippetContentContainTopicSuccess() { + //given + saveTemplateBySnippetContent("tempate1", "public Main {", "new Car();"); + saveTemplateBySnippetContent("tempate2", "private Car", "public Movement"); + saveTemplateBySnippetContent("tempate3", "console.log", "a+b=3"); + + //when + FindAllTemplatesResponse templates = templateService.findContainTopic("Car"); + + //then + assertThat(templates.templates()).hasSize(2); + } } private CreateTemplateRequest makeTemplateRequest(String title) { From d12503532b6434fde80d7037a21fe679004aaa72 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Mon, 5 Aug 2024 21:50:55 +0900 Subject: [PATCH 102/168] =?UTF-8?q?refactor(service):=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EB=AC=B8=EC=9D=84=20=EB=B3=84=EB=8F=84=EC=9D=98=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codezap/category/service/CategoryService.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/category/service/CategoryService.java b/backend/src/main/java/codezap/category/service/CategoryService.java index ce934712b..908ce5189 100644 --- a/backend/src/main/java/codezap/category/service/CategoryService.java +++ b/backend/src/main/java/codezap/category/service/CategoryService.java @@ -50,12 +50,20 @@ private void validateDuplicatedCategory(String categoryName) { } public void deleteById(Long id) { + assertNoTemplates(id); + assertDefaultCategory(id); + categoryRepository.deleteById(id); + } + + private void assertNoTemplates(Long id) { if (templateRepository.existsByCategoryId(id)) { throw new CodeZapException(HttpStatus.BAD_REQUEST, "템플릿이 존재하는 카테고리는 삭제할 수 없습니다."); } + } + + private static void assertDefaultCategory(Long id) { if (id == DEFAULT_CATEGORY) { throw new CodeZapException(HttpStatus.BAD_REQUEST, "기본 카테고리는 삭제할 수 없습니다."); } - categoryRepository.deleteById(id); } } From 1b7a7e66a74319abbf43592c31cf47a9d64e4a58 Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:23:04 +0900 Subject: [PATCH 103/168] =?UTF-8?q?refactor(service):=20=EA=B0=80=EB=8F=85?= =?UTF-8?q?=EC=84=B1=20=EC=A6=9D=EA=B0=80=EB=A5=BC=20=EC=9C=84=ED=95=B4=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/codezap/template/service/TemplateService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 520401551..9265339d5 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -174,6 +174,7 @@ public void deleteById(Long id) { } public FindAllTemplatesResponse findContainTopic(String topic) { - return FindAllTemplatesResponse.from(thumbnailSnippetRepository.searchByTopic(topic)); + List thumbnailSnippets = thumbnailSnippetRepository.searchByTopic(topic); + return FindAllTemplatesResponse.from(thumbnailSnippets); } } From ab771f71ca4b5c0ab15f30c768b348417f7f24bc Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Mon, 5 Aug 2024 23:00:13 +0900 Subject: [PATCH 104/168] =?UTF-8?q?fix(controller):=20requestParam=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20?= =?UTF-8?q?=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TemplateController.java | 3 +- .../controller/TemplateControllerTest.java | 38 ++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/codezap/template/controller/TemplateController.java b/backend/src/main/java/codezap/template/controller/TemplateController.java index 811f68798..b1870325d 100644 --- a/backend/src/main/java/codezap/template/controller/TemplateController.java +++ b/backend/src/main/java/codezap/template/controller/TemplateController.java @@ -2,7 +2,6 @@ import java.net.URI; -import org.apache.commons.lang3.NotImplementedException; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -50,7 +49,7 @@ public ResponseEntity getTemplateById(@PathVariable Long i } @GetMapping("/search") - public ResponseEntity getTemplatesContainTopic(@RequestParam String topic) { + public ResponseEntity getTemplatesContainTopic(@RequestParam("topic") String topic) { return ResponseEntity.ok(templateService.findContainTopic(topic)); } diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 026169e82..122e8b88b 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -469,7 +469,7 @@ void deleteTemplateSuccessWithNotFoundTemplate() { } - private static CreateTemplateRequest createTemplateRequestWithTwoSnippets(String title) { + private CreateTemplateRequest createTemplateRequestWithTwoSnippets(String title) { CreateTemplateRequest templateRequest = new CreateTemplateRequest( title, "description", @@ -489,24 +489,43 @@ void searchTopicSuccess() { //given String topic = "java"; - CreateTemplateRequest templateRequest1 = new CreateTemplateRequest(topic, - List.of(new CreateSnippetRequest("filename", "content", 1))); + CreateCategoryRequest createCategoryRequest1 = new CreateCategoryRequest("category1"); + Long categoryId = categoryService.create(createCategoryRequest1); + + // title에 topic이 포함 + CreateTemplateRequest templateRequest1 = new CreateTemplateRequest( + topic, + "설명", + List.of(new CreateSnippetRequest("filename", "content", 1)), + categoryId, + List.of() + ); RestAssured.given().log().all() .contentType(ContentType.JSON) .body(templateRequest1) .when().post("/templates") .then().log().all(); - - CreateTemplateRequest templateRequest2 = new CreateTemplateRequest("title2", - List.of(new CreateSnippetRequest("filename." + topic, "content", 1))); + // filename에 topic이 포함 + CreateTemplateRequest templateRequest2 = new CreateTemplateRequest( + "title2", + "설명", + List.of(new CreateSnippetRequest("filename." + topic, "content", 1)), + categoryId, + List.of() + ); RestAssured.given().log().all() .contentType(ContentType.JSON) .body(templateRequest2) .when().post("/templates") .then().log().all(); - - CreateTemplateRequest templateRequest3 = new CreateTemplateRequest("title3", - List.of(new CreateSnippetRequest("filename", "// " + topic + " text", 1))); + // code에 topic이 포함 + CreateTemplateRequest templateRequest3 = new CreateTemplateRequest( + "title3", + "설명", + List.of(new CreateSnippetRequest("filename", "// " + topic + " text", 1)), + categoryId, + List.of() + ); RestAssured.given().log().all() .contentType(ContentType.JSON) .body(templateRequest3) @@ -515,6 +534,7 @@ void searchTopicSuccess() { //when RestAssured.given().log().all() + .contentType(ContentType.JSON) .get("/templates/search?topic=" + topic) .then().log().all() .statusCode(200) From dcdec66ad55d78772573b7f3f6d5660ee33b6a31 Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Mon, 5 Aug 2024 23:02:06 +0900 Subject: [PATCH 105/168] =?UTF-8?q?test(service):=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../template/service/TemplateServiceTest.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index bc91a71d9..511d54cbc 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -270,14 +270,18 @@ private Template saveTemplate(CreateTemplateRequest createTemplateRequest) { } private void saveTemplateBySnippetFilename(String templateTitle, String firstFilename, String secondFilename) { + Category category = categoryRepository.save(new Category("category")); CreateTemplateRequest createTemplateRequest = new CreateTemplateRequest( - templateTitle, + templateTitle, "설명", List.of( new CreateSnippetRequest(firstFilename, "content1", 1), new CreateSnippetRequest(secondFilename, "content2", 2) - ) + ), + category.getId(), + List.of() ); - Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title())); + Template savedTemplate = templateRepository.save( + new Template(createTemplateRequest.title(), createTemplateRequest.description(), category)); Snippet savedFirstSnippet = snippetRepository.save(new Snippet(savedTemplate, firstFilename, "content1", 1)); snippetRepository.save(new Snippet(savedTemplate, secondFilename, "content2", 2)); @@ -285,14 +289,18 @@ private void saveTemplateBySnippetFilename(String templateTitle, String firstFil } private void saveTemplateBySnippetContent(String templateTitle, String firstContent, String secondContent) { + Category category = categoryRepository.save(new Category("category")); CreateTemplateRequest createTemplateRequest = new CreateTemplateRequest( - templateTitle, + templateTitle, "설명", List.of( new CreateSnippetRequest("filename1", firstContent, 1), new CreateSnippetRequest("filename2", secondContent, 2) - ) + ), + category.getId(), + List.of() ); - Template savedTemplate = templateRepository.save(new Template(createTemplateRequest.title())); + Template savedTemplate = templateRepository.save( + new Template(createTemplateRequest.title(), createTemplateRequest.description(), category)); Snippet savedFirstSnippet = snippetRepository.save(new Snippet(savedTemplate, "filename1", firstContent, 1)); snippetRepository.save(new Snippet(savedTemplate, "filename2", secondContent, 2)); From b6b9b32fdd56a80bcb35d0681a210e371b954c28 Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Mon, 5 Aug 2024 23:32:01 +0900 Subject: [PATCH 106/168] =?UTF-8?q?test(controller):=20@Nested=EB=A1=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TemplateControllerTest.java | 152 +++++++++++------- 1 file changed, 97 insertions(+), 55 deletions(-) diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 122e8b88b..6ee852b3e 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -483,61 +483,103 @@ private CreateTemplateRequest createTemplateRequestWithTwoSnippets(String title) return templateRequest; } - @Test - @DisplayName("템플릿 토픽 검색 성공") - void searchTopicSuccess() { - //given - String topic = "java"; - - CreateCategoryRequest createCategoryRequest1 = new CreateCategoryRequest("category1"); - Long categoryId = categoryService.create(createCategoryRequest1); - - // title에 topic이 포함 - CreateTemplateRequest templateRequest1 = new CreateTemplateRequest( - topic, - "설명", - List.of(new CreateSnippetRequest("filename", "content", 1)), - categoryId, - List.of() - ); - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .body(templateRequest1) - .when().post("/templates") - .then().log().all(); - // filename에 topic이 포함 - CreateTemplateRequest templateRequest2 = new CreateTemplateRequest( - "title2", - "설명", - List.of(new CreateSnippetRequest("filename." + topic, "content", 1)), - categoryId, - List.of() - ); - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .body(templateRequest2) - .when().post("/templates") - .then().log().all(); - // code에 topic이 포함 - CreateTemplateRequest templateRequest3 = new CreateTemplateRequest( - "title3", - "설명", - List.of(new CreateSnippetRequest("filename", "// " + topic + " text", 1)), - categoryId, - List.of() - ); - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .body(templateRequest3) - .when().post("/templates") - .then().log().all(); + @Nested + @DisplayName("템플릿 토픽 검색") + class searchTopic { - //when - RestAssured.given().log().all() - .contentType(ContentType.JSON) - .get("/templates/search?topic=" + topic) - .then().log().all() - .statusCode(200) - .body("templates.size()", is(3)); + @Test + @DisplayName("템플릿 토픽 검색 성공 : 템플릿 제목에 포함") + void searchTitleTopicSuccess() { + //given + String topic = "java"; + + CreateCategoryRequest createCategoryRequest1 = new CreateCategoryRequest("category1"); + Long categoryId = categoryService.create(createCategoryRequest1); + + // filename에 topic이 포함 + CreateTemplateRequest templateRequest2 = new CreateTemplateRequest( + "title2", + "설명", + List.of(new CreateSnippetRequest("filename." + topic, "content", 1)), + categoryId, + List.of() + ); + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(templateRequest2) + .when().post("/templates") + .then().log().all(); + + //when + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .get("/templates/search?topic=" + topic) + .then().log().all() + .statusCode(200) + .body("templates.size()", is(1)); + } + + @Test + @DisplayName("템플릿 토픽 검색 성공 : 템플릿 코드에 포함") + void searchContentTopicSuccess() { + //given + String topic = "java"; + + CreateCategoryRequest createCategoryRequest1 = new CreateCategoryRequest("category1"); + Long categoryId = categoryService.create(createCategoryRequest1); + + // code에 topic이 포함 + CreateTemplateRequest templateRequest3 = new CreateTemplateRequest( + "title", + "설명", + List.of(new CreateSnippetRequest("filename", "// " + topic + " text", 1)), + categoryId, + List.of() + ); + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(templateRequest3) + .when().post("/templates") + .then().log().all(); + + //when + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .get("/templates/search?topic=" + topic) + .then().log().all() + .statusCode(200) + .body("templates.size()", is(1)); + } + + @Test + @DisplayName("템플릿 토픽 검색 성공 : 스니펫 파일 이름 중에 포함") + void searchFilenameTopicSuccess() { + //given + String topic = "java"; + + CreateCategoryRequest createCategoryRequest1 = new CreateCategoryRequest("category1"); + Long categoryId = categoryService.create(createCategoryRequest1); + + CreateTemplateRequest templateRequest1 = new CreateTemplateRequest( + "title", + "설명", + List.of(new CreateSnippetRequest("file." + topic, "content", 1)), + categoryId, + List.of() + ); + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(templateRequest1) + .when().post("/templates") + .then().log().all(); + + //when + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .get("/templates/search?topic=" + topic) + .then().log().all() + .statusCode(200) + .body("templates.size()", is(1)); + } } } From aa70382b94dadf64b532b4f5e8025b6afc3f5796 Mon Sep 17 00:00:00 2001 From: kyum-q <109158497+kyum-q@users.noreply.github.com> Date: Mon, 5 Aug 2024 23:57:49 +0900 Subject: [PATCH 107/168] =?UTF-8?q?feat(service):=20=ED=83=AC=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=84=A4=EB=AA=85=EC=97=90=20=ED=86=A0=ED=94=BD?= =?UTF-8?q?=EC=9D=B4=20=ED=8F=AC=ED=95=A8=EB=90=9C=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EA=B2=B0=EA=B3=BC=EC=97=90=20=ED=8F=AC?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ThumbnailSnippetRepository.java | 1 + .../controller/TemplateControllerTest.java | 31 ++++++++++++++ .../template/service/TemplateServiceTest.java | 41 +++++++++++++++++-- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java b/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java index 5ec6ad782..41499881c 100644 --- a/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java +++ b/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java @@ -21,6 +21,7 @@ public interface ThumbnailSnippetRepository extends JpaRepository searchByTopic(@Param("topic") String topic); } diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 6ee852b3e..bb19bc50d 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -581,5 +581,36 @@ void searchFilenameTopicSuccess() { .statusCode(200) .body("templates.size()", is(1)); } + + @Test + @DisplayName("템플릿 토픽 검색 성공 : 템플릿 설명에 포함") + void searchDescriptionTopicSuccess() { + //given + String topic = "java"; + + CreateCategoryRequest createCategoryRequest1 = new CreateCategoryRequest("category1"); + Long categoryId = categoryService.create(createCategoryRequest1); + + CreateTemplateRequest templateRequest1 = new CreateTemplateRequest( + "title", + topic + "Login 구현", + List.of(new CreateSnippetRequest("filename", "content", 1)), + categoryId, + List.of() + ); + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .body(templateRequest1) + .when().post("/templates") + .then().log().all(); + + //when + RestAssured.given().log().all() + .contentType(ContentType.JSON) + .get("/templates/search?topic=" + topic) + .then().log().all() + .statusCode(200) + .body("templates.size()", is(1)); + } } } diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 511d54cbc..12d0f59a4 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -173,7 +173,7 @@ void deleteTemplateSuccess() { @DisplayName("템플릿 토픽 검색") class searchContainTopic { @Test - @DisplayName("성공 : 템플릿 제목에 포함") + @DisplayName("템플릿 토픽 검색 성공 : 템플릿 제목에 포함") void findAllTemplatesTitleContainTopicSuccess() { //given saveTemplate(makeTemplateRequest("hello")); @@ -189,7 +189,7 @@ void findAllTemplatesTitleContainTopicSuccess() { } @Test - @DisplayName("성공 : 탬플릿 내에 스니펫 파일명 중 하나라도 포함") + @DisplayName("템플릿 토픽 검색 성공 : 탬플릿 내에 스니펫 파일명 중 하나라도 포함") void findAllSnippetFilenameContainTopicSuccess() { //given saveTemplateBySnippetFilename("tempate1", "login.js", "signup.js"); @@ -204,7 +204,7 @@ void findAllSnippetFilenameContainTopicSuccess() { } @Test - @DisplayName("성공 : 탬플릿 내에 스니펫 코드 중 하나라도 포함") + @DisplayName("템플릿 토픽 검색 성공 : 탬플릿 내에 스니펫 코드 중 하나라도 포함") void findAllSnippetContentContainTopicSuccess() { //given saveTemplateBySnippetContent("tempate1", "public Main {", "new Car();"); @@ -217,6 +217,41 @@ void findAllSnippetContentContainTopicSuccess() { //then assertThat(templates.templates()).hasSize(2); } + + @Test + @DisplayName("템플릿 토픽 검색 성공 : 탬플릿 설명에 포함") + void findAllDescriptionContainTopicSuccess() { + //given + Category category = categoryRepository.save(new Category("category")); + CreateTemplateRequest request1 = new CreateTemplateRequest( + "타이틀", + "Login 구현", + List.of( + new CreateSnippetRequest("filename1", "content1", 1), + new CreateSnippetRequest("filename2", "content2", 2) + ), + category.getId(), + List.of("tag1", "tag2") + ); + saveTemplate(request1); + CreateTemplateRequest request2 = new CreateTemplateRequest( + "타이틀", + "Signup 구현", + List.of( + new CreateSnippetRequest("filename1", "content1", 1), + new CreateSnippetRequest("filename2", "content2", 2) + ), + category.getId(), + List.of("tag1", "tag2") + ); + saveTemplate(request2); + + //when + FindAllTemplatesResponse templates = templateService.findContainTopic("Login"); + + //then + assertThat(templates.templates()).hasSize(1); + } } private CreateTemplateRequest makeTemplateRequest(String title) { From 88718ca245209f0b5183f70bbd16153dc71e0ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=98jminkkk=E2=80=99?= <102847513+jminkkk@users.noreply.github.com> Date: Tue, 6 Aug 2024 00:37:56 +0900 Subject: [PATCH 108/168] =?UTF-8?q?fix(logger):=20MethodExecutionTimeAspec?= =?UTF-8?q?t=EC=9D=98=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=EC=BB=B7=20=ED=91=9C?= =?UTF-8?q?=ED=98=84=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/codezap/global/logger/MethodExecutionTimeAspect.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/codezap/global/logger/MethodExecutionTimeAspect.java b/backend/src/main/java/codezap/global/logger/MethodExecutionTimeAspect.java index dbed5d6de..fbf7b1a87 100644 --- a/backend/src/main/java/codezap/global/logger/MethodExecutionTimeAspect.java +++ b/backend/src/main/java/codezap/global/logger/MethodExecutionTimeAspect.java @@ -13,8 +13,8 @@ public class MethodExecutionTimeAspect { @Around("execution(* codezap..*(..)) && " + - "!execution(* codezap.global.logger.MethodExecutionTimeAspect(..))" + - "!execution(* codezap.global.exception.*.*(..))") + "!within(codezap.global.logger.*) && " + + "!within(codezap.global.exception.*)") public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { if (!log.isInfoEnabled()) { return joinPoint.proceed(); From 05cd1b46821ac5e66ecf696786ade0cc08edcab2 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 6 Aug 2024 13:36:16 +0900 Subject: [PATCH 109/168] =?UTF-8?q?refactor(template):=20findBy=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EC=9D=98=20=EB=A6=AC=ED=84=B4?= =?UTF-8?q?=EA=B0=92=EC=9D=84=20Optional=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/SnippetRepository.java | 3 +- .../template/repository/TagRepository.java | 4 +- .../ThumbnailSnippetRepository.java | 4 +- .../template/service/TemplateService.java | 48 ++++++++++++------- .../repository/SnippetRepositoryTest.java | 5 +- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/backend/src/main/java/codezap/template/repository/SnippetRepository.java b/backend/src/main/java/codezap/template/repository/SnippetRepository.java index 898b5d8ba..bfe6da88e 100644 --- a/backend/src/main/java/codezap/template/repository/SnippetRepository.java +++ b/backend/src/main/java/codezap/template/repository/SnippetRepository.java @@ -1,6 +1,7 @@ package codezap.template.repository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.http.HttpStatus; @@ -17,7 +18,7 @@ default Snippet fetchById(Long id) { List findAllByTemplate(Template template); - Snippet findByTemplateAndOrdinal(Template template, int ordinal); + Optional findByTemplateAndOrdinal(Template template, int ordinal); List findAllByTemplateAndOrdinal(Template template, int ordinal); diff --git a/backend/src/main/java/codezap/template/repository/TagRepository.java b/backend/src/main/java/codezap/template/repository/TagRepository.java index 612dee2b3..8eb30c93e 100644 --- a/backend/src/main/java/codezap/template/repository/TagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TagRepository.java @@ -1,5 +1,7 @@ package codezap.template.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import codezap.template.domain.Tag; @@ -7,5 +9,5 @@ public interface TagRepository extends JpaRepository { boolean existsByName(String name); - Tag findByName(String name); + Optional findByName(String name); } diff --git a/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java b/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java index bb836ed6e..a8fa02650 100644 --- a/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java +++ b/backend/src/main/java/codezap/template/repository/ThumbnailSnippetRepository.java @@ -1,12 +1,14 @@ package codezap.template.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import codezap.template.domain.Template; import codezap.template.domain.ThumbnailSnippet; public interface ThumbnailSnippetRepository extends JpaRepository { - ThumbnailSnippet findByTemplate(Template template); + Optional findByTemplate(Template template); void deleteByTemplateId(Long id); } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 154cd76fb..281c9a08f 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -65,7 +65,8 @@ public Long createTemplate(CreateTemplateRequest createTemplateRequest) { .toList() ); - Snippet thumbnailSnippet = snippetRepository.findByTemplateAndOrdinal(template, FIRST_ORDINAL); + Snippet thumbnailSnippet = snippetRepository.findByTemplateAndOrdinal(template, FIRST_ORDINAL) + .orElseThrow(this::throwNotFoundSnippet); thumbnailSnippetRepository.save(new ThumbnailSnippet(template, thumbnailSnippet)); return template.getId(); } @@ -73,17 +74,17 @@ public Long createTemplate(CreateTemplateRequest createTemplateRequest) { private void createTags(CreateTemplateRequest createTemplateRequest, Template template) { tagRepository.saveAll( createTemplateRequest.tags().stream() - .filter(tagName -> !tagRepository.existsByName(tagName)) - .map(Tag::new) - .toList() + .filter(tagName -> !tagRepository.existsByName(tagName)) + .map(Tag::new) + .toList() ); - templateTagRepository.saveAll( - createTemplateRequest.tags().stream() - .map(tagRepository::findByName) - .map(tag -> new TemplateTag(template, tag)) - .toList() - ); + templateTagRepository.saveAll( + createTemplateRequest.tags().stream() + .map(tag -> tagRepository.findByName(tag).orElseThrow(this::throwNotFoundTag)) + .map(tag -> new TemplateTag(template, tag)) + .toList() + ); } private Snippet createSnippet(CreateSnippetRequest createSnippetRequest, Template template) { @@ -122,7 +123,8 @@ private void updateSnippets(UpdateTemplateRequest updateTemplateRequest, Templat updateTemplateRequest.createSnippets() .forEach(createSnippetRequest -> createSnippet(createSnippetRequest, template)); - ThumbnailSnippet thumbnailSnippet = thumbnailSnippetRepository.findByTemplate(template); + ThumbnailSnippet thumbnailSnippet = thumbnailSnippetRepository.findByTemplate(template) + .orElseThrow(this::throwNotFoundThumbnailSnippet); if (isThumbnailSnippetDeleted(updateTemplateRequest, thumbnailSnippet)) { updateThumbnailSnippet(template, thumbnailSnippet); @@ -156,16 +158,16 @@ private void updateTags(UpdateTemplateRequest updateTemplateRequest, Template te templateTagRepository.deleteAllByTemplateId(template.getId()); tagRepository.saveAll( updateTemplateRequest.tags().stream() - .filter(tagName -> !tagRepository.existsByName(tagName)) - .map(Tag::new) - .toList() + .filter(tagName -> !tagRepository.existsByName(tagName)) + .map(Tag::new) + .toList() ); templateTagRepository.saveAll( updateTemplateRequest.tags().stream() - .map(tagRepository::findByName) - .map(tag -> new TemplateTag(template, tag)) - .toList() + .map(tag -> tagRepository.findByName(tag).orElseThrow(this::throwNotFoundTag)) + .map(tag -> new TemplateTag(template, tag)) + .toList() ); } @@ -183,4 +185,16 @@ public void deleteById(Long id) { templateTagRepository.deleteAllByTemplateId(id); templateRepository.deleteById(id); } + + private CodeZapException throwNotFoundSnippet() { + throw new CodeZapException(HttpStatus.NOT_FOUND, "해당하는 스니펫이 존재하지 않습니다."); + } + + private CodeZapException throwNotFoundTag() { + throw new CodeZapException(HttpStatus.BAD_REQUEST, "해당하는 태그가 존재하지 않습니다."); + } + + private CodeZapException throwNotFoundThumbnailSnippet() { + throw new CodeZapException(HttpStatus.BAD_REQUEST, "해당하는 썸네일 스니펫이 존재하지 않습니다."); + } } diff --git a/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java b/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java index 33dea21f2..d4c3dc05c 100644 --- a/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java +++ b/backend/src/test/java/codezap/template/repository/SnippetRepositoryTest.java @@ -9,12 +9,14 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.transaction.annotation.Transactional; import codezap.category.domain.Category; import codezap.category.repository.CategoryRepository; +import codezap.global.exception.CodeZapException; import codezap.template.domain.Snippet; import codezap.template.domain.Template; @@ -39,7 +41,8 @@ void findOneSnippetSuccessWithTemplateAndOrdinal() { Snippet snippet1 = snippetRepository.save(new Snippet(template, "filename1", "content1", 1)); Snippet snippet2 = snippetRepository.save(new Snippet(template, "filename2", "content2", 2)); - Snippet foundSnippet = snippetRepository.findByTemplateAndOrdinal(template, 2); + Snippet foundSnippet = snippetRepository.findByTemplateAndOrdinal(template, 2) + .orElseThrow(() -> new CodeZapException(HttpStatus.NOT_FOUND, "해당하는 스니펫이 존재하지 않습니다.")); assertAll( () -> assertThat(foundSnippet.getTemplate().getTitle()).isEqualTo(template.getTitle()), From b83da74b617b28d3d7fbc49dcac8cf6ec7258001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Mon, 5 Aug 2024 17:08:27 +0900 Subject: [PATCH 110/168] =?UTF-8?q?refactor(resources):=20=EC=A0=9C?= =?UTF-8?q?=ED=92=88=20=EC=BD=94=EB=93=9C=EC=99=80=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=84=A4=EC=A0=95=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/resources/application-local.yml | 2 -- backend/src/test/resources/application-local.yml | 4 ++++ backend/src/test/resources/application.yml | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 backend/src/test/resources/application-local.yml create mode 100644 backend/src/test/resources/application.yml diff --git a/backend/src/main/resources/application-local.yml b/backend/src/main/resources/application-local.yml index caf383dbb..5e635c67c 100644 --- a/backend/src/main/resources/application-local.yml +++ b/backend/src/main/resources/application-local.yml @@ -1,6 +1,4 @@ spring: - config: - import: classpath:application-db.yml output: ansi: enabled: always diff --git a/backend/src/test/resources/application-local.yml b/backend/src/test/resources/application-local.yml new file mode 100644 index 000000000..5e635c67c --- /dev/null +++ b/backend/src/test/resources/application-local.yml @@ -0,0 +1,4 @@ +spring: + output: + ansi: + enabled: always diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml new file mode 100644 index 000000000..520dec2c9 --- /dev/null +++ b/backend/src/test/resources/application.yml @@ -0,0 +1,5 @@ +spring: + profiles: + active: + - local + - db From b8463e6285b844666a706ec6e8290a2df6aae538 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 6 Aug 2024 14:56:51 +0900 Subject: [PATCH 111/168] =?UTF-8?q?fix(template):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=88=98=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/template/service/TemplateService.java | 11 +++++++---- .../codezap/template/service/TemplateServiceTest.java | 7 +++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 281c9a08f..4ea93e456 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -115,13 +115,16 @@ public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) template.updateTemplate(updateTemplateRequest.title(), updateTemplateRequest.description(), category); updateSnippets(updateTemplateRequest, template); updateTags(updateTemplateRequest, template); - validateSnippetsCount(updateTemplateRequest, template); } private void updateSnippets(UpdateTemplateRequest updateTemplateRequest, Template template) { + validateSnippetsCount(updateTemplateRequest, template); updateTemplateRequest.updateSnippets().forEach(this::updateSnippet); - updateTemplateRequest.createSnippets() - .forEach(createSnippetRequest -> createSnippet(createSnippetRequest, template)); + snippetRepository.saveAll( + updateTemplateRequest.createSnippets().stream() + .map(createSnippetRequest -> createSnippet(createSnippetRequest, template)) + .toList() + ); ThumbnailSnippet thumbnailSnippet = thumbnailSnippetRepository.findByTemplate(template) .orElseThrow(this::throwNotFoundThumbnailSnippet); @@ -172,7 +175,7 @@ private void updateTags(UpdateTemplateRequest updateTemplateRequest, Template te } private void validateSnippetsCount(UpdateTemplateRequest updateTemplateRequest, Template template) { - if (updateTemplateRequest.updateSnippets().size() + updateTemplateRequest.createSnippets().size() + if (updateTemplateRequest.updateSnippets().size() + updateTemplateRequest.deleteSnippetIds().size() != snippetRepository.findAllByTemplate(template).size()) { throw new CodeZapException(HttpStatus.BAD_REQUEST, "스니펫의 정보가 정확하지 않습니다."); } diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 801ca13b4..c7088eac3 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -60,6 +60,7 @@ class TemplateServiceTest { @Autowired private TemplateTagRepository templateTagRepository; + @Autowired private TagRepository tagRepository; @@ -132,13 +133,15 @@ void updateTemplateSuccess() { // when UpdateTemplateRequest updateTemplateRequest = makeUpdateTemplateRequest("updateTitle"); templateService.update(template.getId(), updateTemplateRequest); + Template updateTemplate = templateRepository.fetchById(template.getId()); - List snippets = snippetRepository.findAllByTemplate(template); + List snippets = snippetRepository.findAllByTemplate(updateTemplate); ThumbnailSnippet thumbnailSnippet = thumbnailSnippetRepository.findById(template.getId()).get(); List tags = templateTagRepository.findAllByTemplate(updateTemplate).stream() .map(TemplateTag::getTag) .toList(); + System.out.println(snippets.get(0).getFilename()); // then assertAll( () -> assertThat(updateTemplate.getTitle()).isEqualTo("updateTitle"), @@ -190,7 +193,7 @@ private UpdateTemplateRequest makeUpdateTemplateRequest(String title) { new CreateSnippetRequest("filename4", "content4", 3) ), List.of( - new UpdateSnippetRequest(2L, "filename2", "content2", 1) + new UpdateSnippetRequest(2L, "filenameff", "content2", 1) ), List.of(1L), 2L, From d661424edf46c3bac3a87236ccfcae5b41f6045a Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 6 Aug 2024 14:59:13 +0900 Subject: [PATCH 112/168] =?UTF-8?q?fix(template):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=88=98=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/codezap/template/service/TemplateServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index c7088eac3..f66cf3565 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -135,7 +135,7 @@ void updateTemplateSuccess() { templateService.update(template.getId(), updateTemplateRequest); Template updateTemplate = templateRepository.fetchById(template.getId()); - List snippets = snippetRepository.findAllByTemplate(updateTemplate); + List snippets = snippetRepository.findAllByTemplate(template); ThumbnailSnippet thumbnailSnippet = thumbnailSnippetRepository.findById(template.getId()).get(); List tags = templateTagRepository.findAllByTemplate(updateTemplate).stream() .map(TemplateTag::getTag) From 3206f1d9915953c6ad9c71f915b8df753511049d Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 6 Aug 2024 15:02:28 +0900 Subject: [PATCH 113/168] =?UTF-8?q?fix(template):=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=88=98=EC=A0=95=EC=8B=9C=20=EC=98=AC=EB=B0=94?= =?UTF-8?q?=EB=A5=B8=20=EC=9A=94=EC=B2=AD=EC=9D=84=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/codezap/template/service/TemplateService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index 4ea93e456..d55dc8622 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -115,10 +115,10 @@ public void update(Long templateId, UpdateTemplateRequest updateTemplateRequest) template.updateTemplate(updateTemplateRequest.title(), updateTemplateRequest.description(), category); updateSnippets(updateTemplateRequest, template); updateTags(updateTemplateRequest, template); + validateSnippetsCount(updateTemplateRequest, template); } private void updateSnippets(UpdateTemplateRequest updateTemplateRequest, Template template) { - validateSnippetsCount(updateTemplateRequest, template); updateTemplateRequest.updateSnippets().forEach(this::updateSnippet); snippetRepository.saveAll( updateTemplateRequest.createSnippets().stream() @@ -175,7 +175,7 @@ private void updateTags(UpdateTemplateRequest updateTemplateRequest, Template te } private void validateSnippetsCount(UpdateTemplateRequest updateTemplateRequest, Template template) { - if (updateTemplateRequest.updateSnippets().size() + updateTemplateRequest.deleteSnippetIds().size() + if (updateTemplateRequest.updateSnippets().size() + updateTemplateRequest.createSnippets().size() != snippetRepository.findAllByTemplate(template).size()) { throw new CodeZapException(HttpStatus.BAD_REQUEST, "스니펫의 정보가 정확하지 않습니다."); } From 6f6347c5d30df264c4e62ead68a02e5052039421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Tue, 6 Aug 2024 15:07:30 +0900 Subject: [PATCH 114/168] =?UTF-8?q?refactor(service):=20=EC=BF=A0=ED=82=A4?= =?UTF-8?q?=20=EA=B0=92=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/member/service/AuthService.java | 8 ++++- .../member/service/MemberServiceTest.java | 31 ++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/codezap/member/service/AuthService.java b/backend/src/main/java/codezap/member/service/AuthService.java index 3f7860935..18b151a5a 100644 --- a/backend/src/main/java/codezap/member/service/AuthService.java +++ b/backend/src/main/java/codezap/member/service/AuthService.java @@ -21,6 +21,9 @@ @RequiredArgsConstructor public class AuthService { + private static final String BASIC_AUTH_REGEX = ".+:.+"; + private static final String BASIC_AUTH_DELIMITER = ":"; + private final MemberRepository memberRepository; public MemberDto authorizeByEmailAndPassword(String email, String password) { @@ -50,7 +53,10 @@ private String getAuthCookieValue(Cookie[] cookies) { private String[] decodeCredentials(String encodedCredentials) { byte[] decodedBytes = Base64.getDecoder().decode(encodedCredentials.getBytes(StandardCharsets.UTF_8)); String decodedString = new String(decodedBytes); - return decodedString.split(":"); + if (!decodedString.matches(BASIC_AUTH_REGEX)) { + throwUnauthorized(); + } + return decodedString.split(BASIC_AUTH_DELIMITER); } private CodeZapException throwUnauthorized() { diff --git a/backend/src/test/java/codezap/member/service/MemberServiceTest.java b/backend/src/test/java/codezap/member/service/MemberServiceTest.java index 96dd0b135..57a038b21 100644 --- a/backend/src/test/java/codezap/member/service/MemberServiceTest.java +++ b/backend/src/test/java/codezap/member/service/MemberServiceTest.java @@ -5,12 +5,16 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.stream.Stream; import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.http.HttpHeaders; import codezap.global.exception.CodeZapException; @@ -156,11 +160,7 @@ class CheckLoginTest { void checkLogin() { var member = new Member("code@zap.com", "pw1234", "zappy"); memberRepository.save(member); - var basicAuthCredentials = HttpHeaders.encodeBasicAuth( - member.getEmail(), - member.getPassword(), - StandardCharsets.UTF_8 - ); + var basicAuthCredentials = encodeToBase64(member.getEmail() + ":" + member.getPassword()); var basicAuthCookie = new Cookie(HttpHeaders.AUTHORIZATION, basicAuthCredentials); var cookies = new Cookie[]{basicAuthCookie}; @@ -168,16 +168,12 @@ void checkLogin() { .doesNotThrowAnyException(); } - @Test + @ParameterizedTest @DisplayName("쿠키 인증 실패: 쿠키 값 오류") - void checkLogin_fail_wrong_cookie_value() { + @MethodSource + void checkLogin_fail_wrong_cookie_value(String wrongCredentials) { var member = new Member("code@zap.com", "pw1234", "zappy"); memberRepository.save(member); - var wrongCredentials = HttpHeaders.encodeBasicAuth( - "wrong@email.kr", - "nopassword", - StandardCharsets.UTF_8 - ); var basicAuthCookie = new Cookie(HttpHeaders.AUTHORIZATION, wrongCredentials); var cookies = new Cookie[]{basicAuthCookie}; @@ -185,5 +181,16 @@ void checkLogin_fail_wrong_cookie_value() { .isInstanceOf(CodeZapException.class) .hasMessage("인증에 실패했습니다."); } + + static Stream checkLogin_fail_wrong_cookie_value() { + return Stream.of( + encodeToBase64("wrong@email.kr:nopassword"), + encodeToBase64("Hello world!") + ); + } + + static String encodeToBase64(String plainText) { + return Base64.getEncoder().encodeToString(plainText.getBytes(StandardCharsets.UTF_8)); + } } } From 669bf843b2d842323774be41fd96163bb589e1f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Tue, 6 Aug 2024 15:08:48 +0900 Subject: [PATCH 115/168] =?UTF-8?q?fix(configuration):=20=EC=95=A0?= =?UTF-8?q?=EB=84=88=ED=85=8C=EC=9D=B4=EC=85=98=20=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../codezap/member/configuration/BasicAuthentication.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/src/main/java/codezap/member/configuration/BasicAuthentication.java b/backend/src/main/java/codezap/member/configuration/BasicAuthentication.java index ffb743174..184a1eaf5 100644 --- a/backend/src/main/java/codezap/member/configuration/BasicAuthentication.java +++ b/backend/src/main/java/codezap/member/configuration/BasicAuthentication.java @@ -1,4 +1,11 @@ package codezap.member.configuration; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) public @interface BasicAuthentication { } From 411ab7ee7f98ce72b20f0547d7bb93473c87b426 Mon Sep 17 00:00:00 2001 From: HoeSeong123 Date: Tue, 6 Aug 2024 15:09:14 +0900 Subject: [PATCH 116/168] =?UTF-8?q?refactor(service):=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/java/codezap/template/service/TemplateServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index f66cf3565..460558ea5 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -141,7 +141,6 @@ void updateTemplateSuccess() { .map(TemplateTag::getTag) .toList(); - System.out.println(snippets.get(0).getFilename()); // then assertAll( () -> assertThat(updateTemplate.getTitle()).isEqualTo("updateTitle"), From 1a8bbf1aaaebb6d325643f1ea074ffe8426e9a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Tue, 6 Aug 2024 15:09:26 +0900 Subject: [PATCH 117/168] docs(dto): `@NotEmpty` -> `@NotBlank` --- .../java/codezap/member/dto/LoginRequest.java | 22 ++++++++----------- .../codezap/member/dto/SignupRequest.java | 11 +++++----- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/backend/src/main/java/codezap/member/dto/LoginRequest.java b/backend/src/main/java/codezap/member/dto/LoginRequest.java index 9d99f1936..5dadf93df 100644 --- a/backend/src/main/java/codezap/member/dto/LoginRequest.java +++ b/backend/src/main/java/codezap/member/dto/LoginRequest.java @@ -1,26 +1,22 @@ package codezap.member.dto; -import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import io.swagger.v3.oas.annotations.media.Schema; public record LoginRequest( - @Schema( - description = "이메일", - example = "code@zap.com", - pattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$\n" - ) - @NotEmpty(message = "이메일이 입력되지 않았습니다.") + @Schema(description = "이메일", example = "code@zap.com") + @Email(message = "이메일 형식이 아닙니다.") + @NotBlank(message = "이메일이 입력되지 않았습니다.") @Size(max = 255, message = "이메일은 255자 이하로 입력해주세요.") String email, - @Schema( - description = "비밀번호. 영어와 숫자를 반드시 포함해야 합니다.", - example = "password1234", - pattern = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>]).{8,}$" - ) - @NotEmpty(message = "비밀번호가 입력되지 않았습니다.") + @Schema(description = "비밀번호. 영어와 숫자를 반드시 포함해야 합니다.", example = "password1234") + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>]).{8,}$", message = "영어와 숫자를 포함해야합니다.") + @NotBlank(message = "비밀번호가 입력되지 않았습니다.") @Size(min = 8, max = 255, message = "비밀번호는 8~16자 사이로 입력해주세요.") String password ) { diff --git a/backend/src/main/java/codezap/member/dto/SignupRequest.java b/backend/src/main/java/codezap/member/dto/SignupRequest.java index 7a9ec4899..f992524d2 100644 --- a/backend/src/main/java/codezap/member/dto/SignupRequest.java +++ b/backend/src/main/java/codezap/member/dto/SignupRequest.java @@ -1,6 +1,7 @@ package codezap.member.dto; -import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; @@ -8,19 +9,19 @@ public record SignupRequest( @Schema(description = "이메일", example = "code@zap.com") - @Pattern(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", message = "이메일 형식이 아닙니다.") - @NotEmpty(message = "이메일이 입력되지 않았습니다.") + @Email(message = "이메일 형식이 아닙니다.") + @NotBlank(message = "이메일이 입력되지 않았습니다.") @Size(max = 255, message = "이메일은 255자 이하로 입력해주세요.") String email, @Schema(description = "비밀번호. 영어와 숫자를 반드시 포함해야 합니다.", example = "password1234") @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>]).{8,}$", message = "영어와 숫자를 포함해야합니다.") - @NotEmpty(message = "비밀번호가 입력되지 않았습니다.") + @NotBlank(message = "비밀번호가 입력되지 않았습니다.") @Size(min = 8, max = 255, message = "비밀번호는 8~16자 사이로 입력해주세요.") String password, @Schema(description = "사용자명", example = "zappy") - @NotEmpty(message = "사용자명이 입력되지 않았습니다.") + @NotBlank(message = "사용자명이 입력되지 않았습니다.") @Size(min = 2, max = 255, message = "사용자명은 2~255자 사이로 입력해주세요.") String username ) { From da0fcd9ba27d88446b23bcac0b42fafcbe6d3be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Tue, 6 Aug 2024 15:10:05 +0900 Subject: [PATCH 118/168] =?UTF-8?q?refactor(domain):=20=EC=A7=B1=EC=88=98?= =?UTF-8?q?=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20-=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/codezap/member/domain/Member.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/codezap/member/domain/Member.java b/backend/src/main/java/codezap/member/domain/Member.java index 7aa24cd81..dd144d2c0 100644 --- a/backend/src/main/java/codezap/member/domain/Member.java +++ b/backend/src/main/java/codezap/member/domain/Member.java @@ -9,13 +9,14 @@ import jakarta.persistence.Id; import codezap.global.auditing.BaseTimeEntity; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Getter @EqualsAndHashCode(callSuper = false) @@ -38,7 +39,7 @@ public Member(String email, String password, String username) { this(null, email, password, username); } - public boolean matchPassword(String other) { - return Objects.equals(password, other); + public boolean matchPassword(String password) { + return Objects.equals(this.password, password); } } From ae74ea0dc74f0fe9d4bdeb7f4fe45f6de8dc3f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=AC=EC=9A=B0?= Date: Tue, 6 Aug 2024 15:23:38 +0900 Subject: [PATCH 119/168] =?UTF-8?q?fix(dto):=20=ED=8A=B9=EC=88=98=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EB=A5=BC=20=ED=8F=AC=ED=95=A8=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=95=84=EB=8F=84=20=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/codezap/member/dto/LoginRequest.java | 2 +- backend/src/main/java/codezap/member/dto/SignupRequest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/codezap/member/dto/LoginRequest.java b/backend/src/main/java/codezap/member/dto/LoginRequest.java index 5dadf93df..cb406f64e 100644 --- a/backend/src/main/java/codezap/member/dto/LoginRequest.java +++ b/backend/src/main/java/codezap/member/dto/LoginRequest.java @@ -15,7 +15,7 @@ public record LoginRequest( String email, @Schema(description = "비밀번호. 영어와 숫자를 반드시 포함해야 합니다.", example = "password1234") - @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>]).{8,}$", message = "영어와 숫자를 포함해야합니다.") + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d).{8,}$", message = "영어와 숫자를 포함해야합니다.") @NotBlank(message = "비밀번호가 입력되지 않았습니다.") @Size(min = 8, max = 255, message = "비밀번호는 8~16자 사이로 입력해주세요.") String password diff --git a/backend/src/main/java/codezap/member/dto/SignupRequest.java b/backend/src/main/java/codezap/member/dto/SignupRequest.java index f992524d2..e98848fa1 100644 --- a/backend/src/main/java/codezap/member/dto/SignupRequest.java +++ b/backend/src/main/java/codezap/member/dto/SignupRequest.java @@ -15,7 +15,7 @@ public record SignupRequest( String email, @Schema(description = "비밀번호. 영어와 숫자를 반드시 포함해야 합니다.", example = "password1234") - @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>]).{8,}$", message = "영어와 숫자를 포함해야합니다.") + @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d).{8,}$", message = "영어와 숫자를 포함해야합니다.") @NotBlank(message = "비밀번호가 입력되지 않았습니다.") @Size(min = 8, max = 255, message = "비밀번호는 8~16자 사이로 입력해주세요.") String password, From 085847015e1b57a703119e77528955835ed433ec Mon Sep 17 00:00:00 2001 From: zangsu Date: Mon, 5 Aug 2024 20:58:15 +0900 Subject: [PATCH 120/168] =?UTF-8?q?refactor(template):=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=EC=9D=98=20=ED=85=9C=ED=94=8C=EB=A6=BF=20=ED=83=90?= =?UTF-8?q?=EC=83=89=20API=20=EC=99=80=20=ED=85=9C=ED=94=8C=EB=A6=BF=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20API=20=EB=A5=BC=20=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=B4=20=EA=B8=B0=EC=A1=B4?= =?UTF-8?q?=EC=9D=98=20=ED=85=9C=ED=94=8C=EB=A6=BF=20=ED=83=90=EC=83=89=20?= =?UTF-8?q?API=20=EC=9D=98=20=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpringDocTemplateController.java | 13 +++++-- .../controller/TemplateController.java | 11 +++++- .../response/ExploreTemplatesResponse.java | 39 +++++++++++++++++++ .../response/FindAllTemplatesResponse.java | 34 ---------------- .../template/service/TemplateService.java | 5 ++- .../controller/TemplateControllerTest.java | 4 +- .../template/service/TemplateServiceTest.java | 3 +- 7 files changed, 65 insertions(+), 44 deletions(-) create mode 100644 backend/src/main/java/codezap/template/dto/response/ExploreTemplatesResponse.java diff --git a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java index 8098b8464..198db29cd 100644 --- a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java +++ b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java @@ -7,6 +7,7 @@ import codezap.global.swagger.error.ErrorCase; import codezap.template.dto.request.CreateTemplateRequest; import codezap.template.dto.request.UpdateTemplateRequest; +import codezap.template.dto.response.ExploreTemplatesResponse; import codezap.template.dto.response.FindAllTemplatesResponse; import codezap.template.dto.response.FindTemplateResponse; import io.swagger.v3.oas.annotations.Operation; @@ -39,13 +40,17 @@ public interface SpringDocTemplateController { ResponseEntity create(CreateTemplateRequest createTemplateRequest); @Operation(summary = "템플릿 목록 조회", description = "작성된 모든 템플릿을 조회합니다.") - @ApiResponse(responseCode = "200", description = "조회 성공", - content = {@Content(schema = @Schema(implementation = FindAllTemplatesResponse.class))}) - ResponseEntity getTemplates(); + @ApiResponse(responseCode = "200", description = "템플릿 단건 조회 성공", + content = {@Content(schema = @Schema(implementation = ExploreTemplatesResponse.class))}) + ResponseEntity getTemplates( + Long memberId, + Integer pageNumber, + Integer pageSize + ); @Operation(summary = "템플릿 단건 조회", description = "해당하는 식별자의 템플릿을 조회합니다.") @ApiResponse(responseCode = "200", description = "템플릿 단건 조회 성공", - content = {@Content(schema = @Schema(implementation = FindAllTemplatesResponse.class))}) + content = {@Content(schema = @Schema(implementation = ExploreTemplatesResponse.class))}) @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/templates/1", errorCases = { @ErrorCase(description = "해당하는 id 값인 템플릿이 없는 경우", exampleMessage = "식별자 1에 해당하는 템플릿이 존재하지 않습니다."), }) diff --git a/backend/src/main/java/codezap/template/controller/TemplateController.java b/backend/src/main/java/codezap/template/controller/TemplateController.java index 15298c9b5..a08f4362b 100644 --- a/backend/src/main/java/codezap/template/controller/TemplateController.java +++ b/backend/src/main/java/codezap/template/controller/TemplateController.java @@ -15,6 +15,7 @@ import codezap.global.validation.ValidationSequence; import codezap.template.dto.request.CreateTemplateRequest; import codezap.template.dto.request.UpdateTemplateRequest; +import codezap.template.dto.response.ExploreTemplatesResponse; import codezap.template.dto.response.FindAllTemplatesResponse; import codezap.template.dto.response.FindTemplateResponse; import codezap.template.service.TemplateService; @@ -39,7 +40,15 @@ public ResponseEntity create( } @GetMapping - public ResponseEntity getTemplates() { + public ResponseEntity getTemplates( + Long memberId, + Integer pageNumber, + Integer pageSize) { + return null; + } + + @GetMapping("/explore") + public ResponseEntity explore() { return ResponseEntity.ok(templateService.findAll()); } diff --git a/backend/src/main/java/codezap/template/dto/response/ExploreTemplatesResponse.java b/backend/src/main/java/codezap/template/dto/response/ExploreTemplatesResponse.java new file mode 100644 index 000000000..750dfa00c --- /dev/null +++ b/backend/src/main/java/codezap/template/dto/response/ExploreTemplatesResponse.java @@ -0,0 +1,39 @@ +package codezap.template.dto.response; + +import java.time.LocalDateTime; +import java.util.List; + +import codezap.template.domain.ThumbnailSnippet; +import io.swagger.v3.oas.annotations.media.Schema; + +public record ExploreTemplatesResponse( + @Schema(description = "템플릿 목록") + List templates +) { + public static ExploreTemplatesResponse from(List thumbnailSnippets) { + List templatesBySummaryResponse = thumbnailSnippets.stream() + .map(ItemResponse::from) + .toList(); + return new ExploreTemplatesResponse(templatesBySummaryResponse); + } + + public record ItemResponse( + @Schema(description = "템플릿 식별자", example = "0") + Long id, + @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") + String title, + @Schema(description = "목록 조회 시 보여질 대표 스니펫 정보") + FindThumbnailSnippetResponse thumbnailSnippet, + @Schema(description = "템플릿 수정 시간", example = "2024-11-11 12:00", type = "string") + LocalDateTime modifiedAt + ) { + public static ItemResponse from(ThumbnailSnippet thumbnailSnippet) { + return new ItemResponse( + thumbnailSnippet.getTemplate().getId(), + thumbnailSnippet.getTemplate().getTitle(), + FindThumbnailSnippetResponse.from(thumbnailSnippet.getSnippet()), + thumbnailSnippet.getModifiedAt() + ); + } + } +} diff --git a/backend/src/main/java/codezap/template/dto/response/FindAllTemplatesResponse.java b/backend/src/main/java/codezap/template/dto/response/FindAllTemplatesResponse.java index c0bb51ac0..c327fc7d9 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindAllTemplatesResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindAllTemplatesResponse.java @@ -1,39 +1,5 @@ package codezap.template.dto.response; -import java.time.LocalDateTime; -import java.util.List; - -import codezap.template.domain.ThumbnailSnippet; -import io.swagger.v3.oas.annotations.media.Schema; - public record FindAllTemplatesResponse( - @Schema(description = "템플릿 목록") - List templates ) { - public static FindAllTemplatesResponse from(List thumbnailSnippets) { - List templatesBySummaryResponse = thumbnailSnippets.stream() - .map(ItemResponse::from) - .toList(); - return new FindAllTemplatesResponse(templatesBySummaryResponse); - } - - public record ItemResponse( - @Schema(description = "템플릿 식별자", example = "0") - Long id, - @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") - String title, - @Schema(description = "목록 조회 시 보여질 대표 스니펫 정보") - FindThumbnailSnippetResponse thumbnailSnippet, - @Schema(description = "템플릿 수정 시간", example = "2024-11-11 12:00", type = "string") - LocalDateTime modifiedAt - ) { - public static ItemResponse from(ThumbnailSnippet thumbnailSnippet) { - return new ItemResponse( - thumbnailSnippet.getTemplate().getId(), - thumbnailSnippet.getTemplate().getTitle(), - FindThumbnailSnippetResponse.from(thumbnailSnippet.getSnippet()), - thumbnailSnippet.getModifiedAt() - ); - } - } } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index d55dc8622..ff64c5136 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -19,6 +19,7 @@ import codezap.template.dto.request.CreateTemplateRequest; import codezap.template.dto.request.UpdateSnippetRequest; import codezap.template.dto.request.UpdateTemplateRequest; +import codezap.template.dto.response.ExploreTemplatesResponse; import codezap.template.dto.response.FindAllTemplatesResponse; import codezap.template.dto.response.FindTemplateResponse; import codezap.template.repository.SnippetRepository; @@ -95,8 +96,8 @@ private Snippet createSnippet(CreateSnippetRequest createSnippetRequest, Templat ); } - public FindAllTemplatesResponse findAll() { - return FindAllTemplatesResponse.from(thumbnailSnippetRepository.findAll()); + public ExploreTemplatesResponse findAll() { + return ExploreTemplatesResponse.from(thumbnailSnippetRepository.findAll()); } public FindTemplateResponse findById(Long id) { diff --git a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java index 217695f9d..e1a2910b4 100644 --- a/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java +++ b/backend/src/test/java/codezap/template/controller/TemplateControllerTest.java @@ -188,7 +188,7 @@ void createTemplateFailWithWrongSnippetOrdinal(int firstIndex, int secondIndex) } @Test - @DisplayName("템플릿 전체 조회 성공") + @DisplayName("템플릿 전체 탐색 성공") void findAllTemplatesSuccess() { // given categoryService.create(new CreateCategoryRequest("category")); @@ -199,7 +199,7 @@ void findAllTemplatesSuccess() { // when & then RestAssured.given().log().all() - .get("/templates") + .get("/templates/explore") .then().log().all() .statusCode(200) .body("templates.size()", is(2)); diff --git a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java index 460558ea5..62358d808 100644 --- a/backend/src/test/java/codezap/template/service/TemplateServiceTest.java +++ b/backend/src/test/java/codezap/template/service/TemplateServiceTest.java @@ -26,6 +26,7 @@ import codezap.template.dto.request.CreateTemplateRequest; import codezap.template.dto.request.UpdateSnippetRequest; import codezap.template.dto.request.UpdateTemplateRequest; +import codezap.template.dto.response.ExploreTemplatesResponse; import codezap.template.dto.response.FindAllTemplatesResponse; import codezap.template.dto.response.FindTemplateResponse; import codezap.template.repository.SnippetRepository; @@ -96,7 +97,7 @@ void findAllTemplatesSuccess() { saveTemplate(makeTemplateRequest("title2")); // when - FindAllTemplatesResponse allTemplates = templateService.findAll(); + ExploreTemplatesResponse allTemplates = templateService.findAll(); // then assertThat(allTemplates.templates()).hasSize(2); From fe6d046ceb0e22bb328140c10871175286190fed Mon Sep 17 00:00:00 2001 From: zangsu Date: Tue, 6 Aug 2024 15:23:56 +0900 Subject: [PATCH 121/168] =?UTF-8?q?docs(templates):=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20API=20=EB=AA=85=EC=84=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpringDocTemplateController.java | 15 +++++++++--- .../controller/TemplateController.java | 11 ++++++--- .../response/FindAllTemplatesResponse.java | 23 +++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java index 198db29cd..a9b85d7b0 100644 --- a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java +++ b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java @@ -1,5 +1,7 @@ package codezap.template.controller; +import java.util.List; + import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -39,13 +41,20 @@ public interface SpringDocTemplateController { }) ResponseEntity create(CreateTemplateRequest createTemplateRequest); - @Operation(summary = "템플릿 목록 조회", description = "작성된 모든 템플릿을 조회합니다.") + @Operation(summary = "템플릿 목록 조회", description = """ + 조건에 맞는 모든 템플릿을 조회합니다. + 필터링 조건은 작성자 Id, 카테고리 Id, 태그 목록을 사용할 수 있습니다. + 조회 조건으로 페이지 인덱스, 한 페이지에 들어갈 최대 템플릿의 개수를 변경할 수 있습니다. + 페이지 인덱스는 1, 템플릿 개수는 20개가 기본 값입니다. + """) @ApiResponse(responseCode = "200", description = "템플릿 단건 조회 성공", content = {@Content(schema = @Schema(implementation = ExploreTemplatesResponse.class))}) ResponseEntity getTemplates( - Long memberId, + //Long memberId, Integer pageNumber, - Integer pageSize + Integer pageSize, + Long categoryId, + List tagNames ); @Operation(summary = "템플릿 단건 조회", description = "해당하는 식별자의 템플릿을 조회합니다.") diff --git a/backend/src/main/java/codezap/template/controller/TemplateController.java b/backend/src/main/java/codezap/template/controller/TemplateController.java index a08f4362b..54d5494c8 100644 --- a/backend/src/main/java/codezap/template/controller/TemplateController.java +++ b/backend/src/main/java/codezap/template/controller/TemplateController.java @@ -1,6 +1,7 @@ package codezap.template.controller; import java.net.URI; +import java.util.List; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -10,6 +11,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import codezap.global.validation.ValidationSequence; @@ -41,9 +43,12 @@ public ResponseEntity create( @GetMapping public ResponseEntity getTemplates( - Long memberId, - Integer pageNumber, - Integer pageSize) { + //@RequestParam Long memberId, + @RequestParam(required = false, defaultValue = "1") Integer pageNumber, + @RequestParam(required = false, defaultValue = "20") Integer pageSize, + @RequestParam(required = false, defaultValue = "1") Long categoryId, + @RequestParam(required = false) List tagNames + ) { return null; } diff --git a/backend/src/main/java/codezap/template/dto/response/FindAllTemplatesResponse.java b/backend/src/main/java/codezap/template/dto/response/FindAllTemplatesResponse.java index c327fc7d9..848c4f8d6 100644 --- a/backend/src/main/java/codezap/template/dto/response/FindAllTemplatesResponse.java +++ b/backend/src/main/java/codezap/template/dto/response/FindAllTemplatesResponse.java @@ -1,5 +1,28 @@ package codezap.template.dto.response; +import java.time.LocalDateTime; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + public record FindAllTemplatesResponse( + @Schema(description = "전체 페이지 개수", example = "1") + int totalPage, + @Schema(description = "템플릿 목록") + List templates ) { + + public record ItemResponse( + @Schema(description = "템플릿 식별자", example = "0") + Long id, + @Schema(description = "템플릿 이름", example = "스프링 로그인 구현") + String title, + //todo 문서화 + String description, + //todo 문서화 + List tags, + @Schema(description = "템플릿 수정 시간", example = "2024-11-11 12:00", type = "string") + LocalDateTime modifiedAt + ) { + } } From 0184b174dc1e34b1f7a19d7ff1694c03bd938242 Mon Sep 17 00:00:00 2001 From: zangsu Date: Mon, 5 Aug 2024 20:58:15 +0900 Subject: [PATCH 122/168] =?UTF-8?q?feat(templates):=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC,=20=ED=83=9C=EA=B7=B8=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=85=9C=ED=94=8C=EB=A6=BF=EC=9D=84=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=EB=A7=81=20=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TemplateController.java | 3 +- .../template/repository/TagRepository.java | 11 ++ .../repository/TemplateRepository.java | 14 +++ .../repository/TemplateTagRepository.java | 3 + .../template/service/TemplateService.java | 71 ++++++++++++ .../template/service/TemplateServiceTest.java | 109 ++++++++++++++++++ 6 files changed, 210 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/codezap/template/controller/TemplateController.java b/backend/src/main/java/codezap/template/controller/TemplateController.java index 54d5494c8..5e2afad80 100644 --- a/backend/src/main/java/codezap/template/controller/TemplateController.java +++ b/backend/src/main/java/codezap/template/controller/TemplateController.java @@ -3,6 +3,7 @@ import java.net.URI; import java.util.List; +import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -49,7 +50,7 @@ public ResponseEntity getTemplates( @RequestParam(required = false, defaultValue = "1") Long categoryId, @RequestParam(required = false) List tagNames ) { - return null; + return ResponseEntity.ok(templateService.findAllBy(PageRequest.of(pageNumber - 1, pageSize), categoryId, tagNames)); } @GetMapping("/explore") diff --git a/backend/src/main/java/codezap/template/repository/TagRepository.java b/backend/src/main/java/codezap/template/repository/TagRepository.java index 8eb30c93e..8ff7f3b85 100644 --- a/backend/src/main/java/codezap/template/repository/TagRepository.java +++ b/backend/src/main/java/codezap/template/repository/TagRepository.java @@ -1,13 +1,24 @@ package codezap.template.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.http.HttpStatus; +import codezap.global.exception.CodeZapException; import codezap.template.domain.Tag; public interface TagRepository extends JpaRepository { + + default Tag fetchById(Long id) { + return findById(id).orElseThrow( + () -> new CodeZapException(HttpStatus.NOT_FOUND, "식별자 " + id + "에 해당하는 태그가 존재하지 않습니다.")); + } + boolean existsByName(String name); Optional findByName(String name); + + List findByNameIn(List tagNames); } diff --git a/backend/src/main/java/codezap/template/repository/TemplateRepository.java b/backend/src/main/java/codezap/template/repository/TemplateRepository.java index 127eef4cf..578faff36 100644 --- a/backend/src/main/java/codezap/template/repository/TemplateRepository.java +++ b/backend/src/main/java/codezap/template/repository/TemplateRepository.java @@ -1,5 +1,10 @@ package codezap.template.repository; +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.http.HttpStatus; @@ -14,4 +19,13 @@ default Template fetchById(Long id) { } boolean existsByCategoryId(Long categoryId); + + Page