From 5dd29239c91ab86f38a8190c875024a12a567e7f Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Mon, 30 Jun 2025 23:07:15 +0800 Subject: [PATCH] Add category module and require post category --- .../com/openisle/config/SecurityConfig.java | 5 +- .../controller/CategoryController.java | 58 +++++++++++++++++++ .../openisle/controller/PostController.java | 5 +- .../java/com/openisle/model/Category.java | 20 +++++++ src/main/java/com/openisle/model/Post.java | 4 ++ .../repository/CategoryRepository.java | 7 +++ .../com/openisle/service/CategoryService.java | 33 +++++++++++ .../com/openisle/service/PostService.java | 8 ++- .../controller/PostControllerTest.java | 13 ++++- .../ComplexFlowIntegrationTest.java | 17 +++++- 10 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/openisle/controller/CategoryController.java create mode 100644 src/main/java/com/openisle/model/Category.java create mode 100644 src/main/java/com/openisle/repository/CategoryRepository.java create mode 100644 src/main/java/com/openisle/service/CategoryService.java diff --git a/src/main/java/com/openisle/config/SecurityConfig.java b/src/main/java/com/openisle/config/SecurityConfig.java index 5e3f7ff26..33b0c1dee 100644 --- a/src/main/java/com/openisle/config/SecurityConfig.java +++ b/src/main/java/com/openisle/config/SecurityConfig.java @@ -68,6 +68,9 @@ public class SecurityConfig { .requestMatchers(HttpMethod.POST, "/api/auth/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/posts/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/comments/**").permitAll() + .requestMatchers(HttpMethod.GET, "/api/categories/**").permitAll() + .requestMatchers(HttpMethod.POST, "/api/categories/**").hasAuthority("ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/categories/**").hasAuthority("ADMIN") .requestMatchers("/api/admin/**").hasAuthority("ADMIN") .anyRequest().authenticated() ) @@ -84,7 +87,7 @@ public class SecurityConfig { String uri = request.getRequestURI(); boolean publicGet = "GET".equalsIgnoreCase(request.getMethod()) && - (uri.startsWith("/api/posts") || uri.startsWith("/api/comments")); + (uri.startsWith("/api/posts") || uri.startsWith("/api/comments") || uri.startsWith("/api/categories")); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); diff --git a/src/main/java/com/openisle/controller/CategoryController.java b/src/main/java/com/openisle/controller/CategoryController.java new file mode 100644 index 000000000..5d2882ecf --- /dev/null +++ b/src/main/java/com/openisle/controller/CategoryController.java @@ -0,0 +1,58 @@ +package com.openisle.controller; + +import com.openisle.model.Category; +import com.openisle.service.CategoryService; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api/categories") +@RequiredArgsConstructor +public class CategoryController { + private final CategoryService categoryService; + + @PostMapping + public CategoryDto create(@RequestBody CategoryRequest req) { + Category c = categoryService.createCategory(req.getName()); + return toDto(c); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + categoryService.deleteCategory(id); + } + + @GetMapping + public List list() { + return categoryService.listCategories().stream() + .map(this::toDto) + .collect(Collectors.toList()); + } + + @GetMapping("/{id}") + public CategoryDto get(@PathVariable Long id) { + return toDto(categoryService.getCategory(id)); + } + + private CategoryDto toDto(Category c) { + CategoryDto dto = new CategoryDto(); + dto.setId(c.getId()); + dto.setName(c.getName()); + return dto; + } + + @Data + private static class CategoryRequest { + private String name; + } + + @Data + private static class CategoryDto { + private Long id; + private String name; + } +} diff --git a/src/main/java/com/openisle/controller/PostController.java b/src/main/java/com/openisle/controller/PostController.java index 64c2d364d..00517aca5 100644 --- a/src/main/java/com/openisle/controller/PostController.java +++ b/src/main/java/com/openisle/controller/PostController.java @@ -39,7 +39,7 @@ public class PostController { @PostMapping public ResponseEntity createPost(@RequestBody PostRequest req, Authentication auth) { - Post post = postService.createPost(auth.getName(), req.getTitle(), req.getContent()); + Post post = postService.createPost(auth.getName(), req.getCategoryId(), req.getTitle(), req.getContent()); return ResponseEntity.ok(toDto(post)); } @@ -61,6 +61,7 @@ public class PostController { dto.setContent(post.getContent()); dto.setCreatedAt(post.getCreatedAt()); dto.setAuthor(post.getAuthor().getUsername()); + dto.setCategory(post.getCategory().getName()); dto.setViews(post.getViews()); List reactions = reactionService.getReactionsForPost(post.getId()) @@ -119,6 +120,7 @@ public class PostController { @Data private static class PostRequest { + private Long categoryId; private String title; private String content; } @@ -130,6 +132,7 @@ public class PostController { private String content; private LocalDateTime createdAt; private String author; + private String category; private long views; private List comments; private List reactions; diff --git a/src/main/java/com/openisle/model/Category.java b/src/main/java/com/openisle/model/Category.java new file mode 100644 index 000000000..5c9a8dc64 --- /dev/null +++ b/src/main/java/com/openisle/model/Category.java @@ -0,0 +1,20 @@ +package com.openisle.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@Table(name = "categories") +public class Category { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String name; +} diff --git a/src/main/java/com/openisle/model/Post.java b/src/main/java/com/openisle/model/Post.java index a923e44c8..adbce523d 100644 --- a/src/main/java/com/openisle/model/Post.java +++ b/src/main/java/com/openisle/model/Post.java @@ -30,6 +30,10 @@ public class Post { @JoinColumn(name = "author_id") private User author; + @ManyToOne(optional = false, fetch = FetchType.LAZY) + @JoinColumn(name = "category_id") + private Category category; + @Column(nullable = false) private long views = 0; diff --git a/src/main/java/com/openisle/repository/CategoryRepository.java b/src/main/java/com/openisle/repository/CategoryRepository.java new file mode 100644 index 000000000..443f08e5f --- /dev/null +++ b/src/main/java/com/openisle/repository/CategoryRepository.java @@ -0,0 +1,7 @@ +package com.openisle.repository; + +import com.openisle.model.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CategoryRepository extends JpaRepository { +} diff --git a/src/main/java/com/openisle/service/CategoryService.java b/src/main/java/com/openisle/service/CategoryService.java new file mode 100644 index 000000000..144998b97 --- /dev/null +++ b/src/main/java/com/openisle/service/CategoryService.java @@ -0,0 +1,33 @@ +package com.openisle.service; + +import com.openisle.model.Category; +import com.openisle.repository.CategoryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CategoryService { + private final CategoryRepository categoryRepository; + + public Category createCategory(String name) { + Category category = new Category(); + category.setName(name); + return categoryRepository.save(category); + } + + public void deleteCategory(Long id) { + categoryRepository.deleteById(id); + } + + public Category getCategory(Long id) { + return categoryRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("Category not found")); + } + + public List listCategories() { + return categoryRepository.findAll(); + } +} diff --git a/src/main/java/com/openisle/service/PostService.java b/src/main/java/com/openisle/service/PostService.java index a7dc70517..32997e772 100644 --- a/src/main/java/com/openisle/service/PostService.java +++ b/src/main/java/com/openisle/service/PostService.java @@ -2,8 +2,10 @@ package com.openisle.service; import com.openisle.model.Post; import com.openisle.model.User; +import com.openisle.model.Category; import com.openisle.repository.PostRepository; import com.openisle.repository.UserRepository; +import com.openisle.repository.CategoryRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -14,14 +16,18 @@ import java.util.List; public class PostService { private final PostRepository postRepository; private final UserRepository userRepository; + private final CategoryRepository categoryRepository; - public Post createPost(String username, String title, String content) { + public Post createPost(String username, Long categoryId, String title, String content) { User author = userRepository.findByUsername(username) .orElseThrow(() -> new IllegalArgumentException("User not found")); + Category category = categoryRepository.findById(categoryId) + .orElseThrow(() -> new IllegalArgumentException("Category not found")); Post post = new Post(); post.setTitle(title); post.setContent(content); post.setAuthor(author); + post.setCategory(category); return postRepository.save(post); } diff --git a/src/test/java/com/openisle/controller/PostControllerTest.java b/src/test/java/com/openisle/controller/PostControllerTest.java index 9a13e21f6..d2155896e 100644 --- a/src/test/java/com/openisle/controller/PostControllerTest.java +++ b/src/test/java/com/openisle/controller/PostControllerTest.java @@ -2,6 +2,7 @@ package com.openisle.controller; import com.openisle.model.Post; import com.openisle.model.User; +import com.openisle.model.Category; import com.openisle.service.PostService; import com.openisle.service.CommentService; import com.openisle.service.ReactionService; @@ -41,18 +42,22 @@ class PostControllerTest { void createAndGetPost() throws Exception { User user = new User(); user.setUsername("alice"); + Category cat = new Category(); + cat.setId(1L); + cat.setName("tech"); Post post = new Post(); post.setId(1L); post.setTitle("t"); post.setContent("c"); post.setCreatedAt(LocalDateTime.now()); post.setAuthor(user); - Mockito.when(postService.createPost(eq("alice"), eq("t"), eq("c"))).thenReturn(post); + post.setCategory(cat); + Mockito.when(postService.createPost(eq("alice"), eq(1L), eq("t"), eq("c"))).thenReturn(post); Mockito.when(postService.getPost(1L)).thenReturn(post); mockMvc.perform(post("/api/posts") .contentType("application/json") - .content("{\"title\":\"t\",\"content\":\"c\"}") + .content("{\"title\":\"t\",\"content\":\"c\",\"categoryId\":1}") .principal(new UsernamePasswordAuthenticationToken("alice", "p"))) .andExpect(status().isOk()) .andExpect(jsonPath("$.title").value("t")); @@ -66,12 +71,16 @@ class PostControllerTest { void listPosts() throws Exception { User user = new User(); user.setUsername("bob"); + Category cat = new Category(); + cat.setId(1L); + cat.setName("tech"); Post post = new Post(); post.setId(2L); post.setTitle("hello"); post.setContent("world"); post.setCreatedAt(LocalDateTime.now()); post.setAuthor(user); + post.setCategory(cat); Mockito.when(postService.listPosts()).thenReturn(List.of(post)); mockMvc.perform(get("/api/posts")) diff --git a/src/test/java/com/openisle/integration/ComplexFlowIntegrationTest.java b/src/test/java/com/openisle/integration/ComplexFlowIntegrationTest.java index 525d19e2f..e5bc14369 100644 --- a/src/test/java/com/openisle/integration/ComplexFlowIntegrationTest.java +++ b/src/test/java/com/openisle/integration/ComplexFlowIntegrationTest.java @@ -1,7 +1,9 @@ package com.openisle.integration; import com.openisle.model.User; +import com.openisle.model.Category; import com.openisle.repository.UserRepository; +import com.openisle.repository.CategoryRepository; import com.openisle.service.EmailService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +26,9 @@ class ComplexFlowIntegrationTest { @Autowired private UserRepository users; + @Autowired + private CategoryRepository categories; + @MockBean private EmailService emailService; @@ -52,8 +57,12 @@ class ComplexFlowIntegrationTest { String t1 = registerAndLogin("alice", "a@example.com"); String t2 = registerAndLogin("bob", "b@example.com"); + Category cat = new Category(); + cat.setName("general"); + cat = categories.save(cat); + ResponseEntity postResp = postJson("/api/posts", - Map.of("title", "Hello", "content", "World"), t1); + Map.of("title", "Hello", "content", "World", "categoryId", cat.getId()), t1); Long postId = ((Number)postResp.getBody().get("id")).longValue(); ResponseEntity c1Resp = postJson("/api/posts/" + postId + "/comments", @@ -87,8 +96,12 @@ class ComplexFlowIntegrationTest { String t1 = registerAndLogin("carol", "c@example.com"); String t2 = registerAndLogin("dave", "d@example.com"); + Category cat = new Category(); + cat.setName("general"); + cat = categories.save(cat); + ResponseEntity postResp = postJson("/api/posts", - Map.of("title", "React", "content", "Test"), t1); + Map.of("title", "React", "content", "Test", "categoryId", cat.getId()), t1); Long postId = ((Number)postResp.getBody().get("id")).longValue(); postJson("/api/posts/" + postId + "/reactions",