Skip to content

Commit 0ad11e7

Browse files
authored
Merge branch 'dev' into Feat/188
2 parents 3f44247 + a4528bf commit 0ad11e7

File tree

18 files changed

+1164
-31
lines changed

18 files changed

+1164
-31
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.back.domain.board.post.controller;
2+
3+
import com.back.domain.board.post.dto.CategoryRequest;
4+
import com.back.domain.board.post.dto.CategoryResponse;
5+
import com.back.domain.board.post.service.PostCategoryService;
6+
import com.back.global.common.dto.RsData;
7+
import com.back.global.security.user.CustomUserDetails;
8+
import jakarta.validation.Valid;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.http.HttpStatus;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
13+
import org.springframework.web.bind.annotation.*;
14+
15+
import java.util.List;
16+
17+
@RestController
18+
@RequestMapping("/api/posts/categories")
19+
@RequiredArgsConstructor
20+
public class PostCategoryController implements PostCategoryControllerDocs {
21+
private final PostCategoryService postCategoryService;
22+
23+
// 카테고리 생성
24+
@PostMapping
25+
public ResponseEntity<RsData<CategoryResponse>> createCategory(
26+
@RequestBody @Valid CategoryRequest request,
27+
@AuthenticationPrincipal CustomUserDetails user
28+
) {
29+
CategoryResponse response = postCategoryService.createCategory(request, user.getUserId());
30+
return ResponseEntity
31+
.status(HttpStatus.CREATED)
32+
.body(RsData.success(
33+
"카테고리가 생성되었습니다.",
34+
response
35+
));
36+
}
37+
38+
// 카테고리 전체 조회
39+
@GetMapping
40+
public ResponseEntity<RsData<List<CategoryResponse>>> getAllCategories() {
41+
List<CategoryResponse> response = postCategoryService.getAllCategories();
42+
return ResponseEntity
43+
.status(HttpStatus.OK)
44+
.body(RsData.success(
45+
"카테고리 목록이 조회되었습니다.",
46+
response
47+
));
48+
}
49+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package com.back.domain.board.post.controller;
2+
3+
import com.back.domain.board.post.dto.CategoryRequest;
4+
import com.back.domain.board.post.dto.CategoryResponse;
5+
import com.back.global.common.dto.RsData;
6+
import com.back.global.security.user.CustomUserDetails;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.media.Content;
9+
import io.swagger.v3.oas.annotations.media.ExampleObject;
10+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
12+
import io.swagger.v3.oas.annotations.tags.Tag;
13+
import org.springframework.http.ResponseEntity;
14+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
15+
import org.springframework.web.bind.annotation.RequestBody;
16+
17+
import java.util.List;
18+
19+
@Tag(name = "Category API", description = "게시글 카테고리 관련 API")
20+
public interface PostCategoryControllerDocs {
21+
22+
@Operation(
23+
summary = "카테고리 생성",
24+
description = "로그인한 사용자가 새 카테고리를 등록합니다."
25+
)
26+
@ApiResponses({
27+
@ApiResponse(
28+
responseCode = "201",
29+
description = "카테고리 생성 성공",
30+
content = @Content(
31+
mediaType = "application/json",
32+
examples = @ExampleObject(value = """
33+
{
34+
"success": true,
35+
"code": "SUCCESS_201",
36+
"message": "카테고리가 생성되었습니다.",
37+
"data": {
38+
"id": 80,
39+
"name": "수학 II",
40+
"type": "SUBJECT"
41+
}
42+
}
43+
""")
44+
)
45+
),
46+
@ApiResponse(
47+
responseCode = "400",
48+
description = "잘못된 요청 (필드 누락 등)",
49+
content = @Content(
50+
mediaType = "application/json",
51+
examples = @ExampleObject(value = """
52+
{
53+
"success": false,
54+
"code": "COMMON_400",
55+
"message": "잘못된 요청입니다.",
56+
"data": null
57+
}
58+
""")
59+
)
60+
),
61+
@ApiResponse(
62+
responseCode = "401",
63+
description = "인증 실패 (Access Token 없음/만료/잘못됨)",
64+
content = @Content(
65+
mediaType = "application/json",
66+
examples = {
67+
@ExampleObject(name = "토큰 없음", value = """
68+
{
69+
"success": false,
70+
"code": "AUTH_001",
71+
"message": "인증이 필요합니다.",
72+
"data": null
73+
}
74+
"""),
75+
@ExampleObject(name = "유효하지 않은 토큰", value = """
76+
{
77+
"success": false,
78+
"code": "AUTH_002",
79+
"message": "유효하지 않은 액세스 토큰입니다.",
80+
"data": null
81+
}
82+
"""),
83+
@ExampleObject(name = "만료된 토큰", value = """
84+
{
85+
"success": false,
86+
"code": "AUTH_004",
87+
"message": "만료된 액세스 토큰입니다.",
88+
"data": null
89+
}
90+
""")
91+
}
92+
)
93+
),
94+
@ApiResponse(
95+
responseCode = "404",
96+
description = "존재하지 않는 사용자",
97+
content = @Content(
98+
mediaType = "application/json",
99+
examples = @ExampleObject(value = """
100+
{
101+
"success": false,
102+
"code": "USER_001",
103+
"message": "존재하지 않는 사용자입니다.",
104+
"data": null
105+
}
106+
""")
107+
)
108+
),
109+
@ApiResponse(
110+
responseCode = "409",
111+
description = "이미 존재하는 카테고리 이름",
112+
content = @Content(
113+
mediaType = "application/json",
114+
examples = @ExampleObject(value = """
115+
{
116+
"success": false,
117+
"code": "POST_004",
118+
"message": "이미 존재하는 카테고리입니다.",
119+
"data": null
120+
}
121+
""")
122+
)
123+
),
124+
@ApiResponse(
125+
responseCode = "500",
126+
description = "서버 내부 오류",
127+
content = @Content(
128+
mediaType = "application/json",
129+
examples = @ExampleObject(value = """
130+
{
131+
"success": false,
132+
"code": "COMMON_500",
133+
"message": "서버 오류가 발생했습니다.",
134+
"data": null
135+
}
136+
""")
137+
)
138+
)
139+
})
140+
ResponseEntity<RsData<CategoryResponse>> createCategory(
141+
@RequestBody CategoryRequest request,
142+
@AuthenticationPrincipal CustomUserDetails user
143+
);
144+
145+
@Operation(
146+
summary = "카테고리 전체 조회",
147+
description = "현재 등록된 모든 카테고리 목록을 조회합니다."
148+
)
149+
@ApiResponses({
150+
@ApiResponse(
151+
responseCode = "200",
152+
description = "카테고리 목록 조회 성공",
153+
content = @Content(
154+
mediaType = "application/json",
155+
examples = @ExampleObject(value = """
156+
{
157+
"success": true,
158+
"code": "SUCCESS_200",
159+
"message": "카테고리 목록이 조회되었습니다.",
160+
"data": [
161+
{ "id": 1, "name": "프론트엔드", "type": "SUBJECT" },
162+
{ "id": 2, "name": "백엔드", "type": "SUBJECT" },
163+
{ "id": 3, "name": "CS", "type": "SUBJECT" },
164+
{ "id": 74, "name": "중학생", "type": "DEMOGRAPHIC" },
165+
{ "id": 78, "name": "2~4명", "type": "GROUP_SIZE" }
166+
]
167+
}
168+
""")
169+
)
170+
),
171+
@ApiResponse(
172+
responseCode = "500",
173+
description = "서버 내부 오류",
174+
content = @Content(
175+
mediaType = "application/json",
176+
examples = @ExampleObject(value = """
177+
{
178+
"success": false,
179+
"code": "COMMON_500",
180+
"message": "서버 오류가 발생했습니다.",
181+
"data": null
182+
}
183+
""")
184+
)
185+
)
186+
})
187+
ResponseEntity<RsData<List<CategoryResponse>>> getAllCategories();
188+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.back.domain.board.post.dto;
2+
3+
import com.back.domain.board.post.enums.CategoryType;
4+
import jakarta.validation.constraints.NotBlank;
5+
import jakarta.validation.constraints.NotNull;
6+
7+
/**
8+
* 카테고리 생성 요청 DTO
9+
*
10+
* @param name 카테고리 이름
11+
* @param type 카테고리 분류
12+
*/
13+
public record CategoryRequest(
14+
@NotBlank String name,
15+
@NotNull CategoryType type
16+
) {
17+
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
package com.back.domain.board.post.dto;
22

33
import com.back.domain.board.post.entity.PostCategory;
4+
import com.back.domain.board.post.enums.CategoryType;
45
import com.querydsl.core.annotations.QueryProjection;
56

67
/**
78
* 카테고리 응답 DTO
89
*
910
* @param id 카테고리 ID
1011
* @param name 카테고리 이름
12+
* @param type 카테고리 분류
1113
*/
1214
public record CategoryResponse(
1315
Long id,
14-
String name
16+
String name,
17+
CategoryType type
1518
) {
1619
@QueryProjection
1720
public CategoryResponse {}
1821

1922
public static CategoryResponse from(PostCategory category) {
2023
return new CategoryResponse(
2124
category.getId(),
22-
category.getName()
25+
category.getName(),
26+
category.getType()
2327
);
2428
}
2529
}

src/main/java/com/back/domain/board/post/entity/PostCategory.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package com.back.domain.board.post.entity;
22

3+
import com.back.domain.board.post.enums.CategoryType;
34
import com.back.global.entity.BaseEntity;
4-
import jakarta.persistence.CascadeType;
5-
import jakarta.persistence.Entity;
6-
import jakarta.persistence.OneToMany;
5+
import jakarta.persistence.*;
76
import lombok.Getter;
87
import lombok.NoArgsConstructor;
98

@@ -16,12 +15,23 @@
1615
public class PostCategory extends BaseEntity {
1716
private String name;
1817

18+
@Enumerated(EnumType.STRING)
19+
@Column(nullable = false)
20+
private CategoryType type;
21+
1922
@OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
2023
private List<PostCategoryMapping> postCategoryMappings;
2124

2225
// -------------------- 생성자 --------------------
2326
public PostCategory(String name) {
2427
this.name = name;
28+
this.type = CategoryType.SUBJECT;
29+
this.postCategoryMappings = new ArrayList<>();
30+
}
31+
32+
public PostCategory(String name, CategoryType type) {
33+
this.name = name;
34+
this.type = type;
2535
this.postCategoryMappings = new ArrayList<>();
2636
}
2737
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.back.domain.board.post.enums;
2+
3+
public enum CategoryType {
4+
SUBJECT, // 과목
5+
DEMOGRAPHIC, // 연령대
6+
GROUP_SIZE // 인원
7+
}

src/main/java/com/back/domain/board/post/repository/PostCategoryRepository.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66

77
@Repository
88
public interface PostCategoryRepository extends JpaRepository<PostCategory, Long> {
9+
boolean existsByName(String name);
910
}

src/main/java/com/back/domain/board/post/repository/PostRepositoryImpl.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,11 @@ private void injectCategories(List<PostListResponse> results) {
198198
List<Tuple> categoryTuples = queryFactory
199199
.select(
200200
categoryMapping.post.id,
201-
new QCategoryResponse(categoryMapping.category.id, categoryMapping.category.name)
201+
new QCategoryResponse(
202+
categoryMapping.category.id,
203+
categoryMapping.category.name,
204+
categoryMapping.category.type
205+
)
202206
)
203207
.from(categoryMapping)
204208
.where(categoryMapping.post.id.in(postIds))

0 commit comments

Comments
 (0)