Skip to content

Commit e97fe04

Browse files
authored
[DEV-13] FAQ API 구현 (#62)
1 parent 5988c87 commit e97fe04

27 files changed

+674
-31
lines changed

src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthService authSer
3838
API_PREFIX + "/clubs/**",
3939
API_PREFIX + "/notices/**",
4040
API_PREFIX + "/banners/**",
41-
API_PREFIX + "/documents/**")
41+
API_PREFIX + "/documents/**",
42+
API_PREFIX + "/questions/**")
4243
.permitAll()
4344
.antMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**").permitAll()
4445
.anyRequest()

src/main/java/ddingdong/ddingdongBE/common/exception/ErrorMessage.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
@Getter
77
@RequiredArgsConstructor
88
public enum ErrorMessage {
9-
AUTHENTICATION_ERROR("알 수 없는 인증 관련 오류가 발생하였습니다."),
109
INTERNAL_SERVER_ERROR("서버에 문제가 발생했습니다."),
1110
ILLEGAL_CLUB_LOCATION_PATTERN("올바르지 않은 동아리 위치 양식입니다."),
1211
ILLEGAL_CLUB_PHONE_NUMBER_PATTERN("올바르지 않은 동아리 전화번호 양식입니다."),
@@ -26,7 +25,8 @@ public enum ErrorMessage {
2625
NO_SUCH_BANNER("해당 배너가 존재하지 않습니다."),
2726
NO_SUCH_FIX("해당 수리 신청서가 존재하지 않습니다."),
2827
NO_SUCH_FIX_ZONE_COMMENT("존재하지 않는 픽스존 댓글입니다."),
29-
NO_SUCH_DOCUMENT("해당 자료가 존재하지 않습니다.");
28+
NO_SUCH_DOCUMENT("해당 자료가 존재하지 않습니다."),
29+
NO_SUCH_QUESTION("해당 질문이 존재하지 않습니다.");
3030

3131
private final String text;
3232
}

src/main/java/ddingdong/ddingdongBE/domain/documents/api/AdminDocumentApi.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ddingdong.ddingdongBE.domain.documents.api;
22

33

4+
import ddingdong.ddingdongBE.auth.PrincipalDetails;
45
import ddingdong.ddingdongBE.domain.documents.controller.dto.request.GenerateDocumentRequest;
56
import ddingdong.ddingdongBE.domain.documents.controller.dto.request.ModifyDocumentRequest;
67
import ddingdong.ddingdongBE.domain.documents.controller.dto.response.AdminDetailDocumentResponse;
@@ -11,6 +12,7 @@
1112
import java.util.List;
1213
import org.springframework.http.HttpStatus;
1314
import org.springframework.http.MediaType;
15+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1416
import org.springframework.web.bind.annotation.DeleteMapping;
1517
import org.springframework.web.bind.annotation.GetMapping;
1618
import org.springframework.web.bind.annotation.ModelAttribute;
@@ -30,8 +32,10 @@ public interface AdminDocumentApi {
3032
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
3133
@ResponseStatus(HttpStatus.CREATED)
3234
@SecurityRequirement(name = "AccessToken")
33-
void generateDocument(@ModelAttribute GenerateDocumentRequest generateDocumentRequest,
34-
@RequestPart(name = "uploadFiles") List<MultipartFile> uploadFiles);
35+
void generateDocument(
36+
@AuthenticationPrincipal PrincipalDetails principalDetails,
37+
@ModelAttribute GenerateDocumentRequest generateDocumentRequest,
38+
@RequestPart(name = "uploadFiles") List<MultipartFile> uploadFiles);
3539

3640
@Operation(summary = "어드민 자료실 목록 조회 API")
3741
@GetMapping

src/main/java/ddingdong/ddingdongBE/domain/documents/api/DocumentApi.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,11 @@ public interface DocumentApi {
2020
@Operation(summary = "자료실 목록 조회 API")
2121
@GetMapping
2222
@ResponseStatus(HttpStatus.OK)
23-
@SecurityRequirement(name = "AccessToken")
2423
List<DocumentResponse> getAllDocuments();
2524

2625
@Operation(summary = "자료실 상세 조회 API")
2726
@GetMapping("/{documentId}")
2827
@ResponseStatus(HttpStatus.OK)
29-
@SecurityRequirement(name = "AccessToken")
3028
DetailDocumentResponse getDetailDocument(@PathVariable Long documentId);
3129

3230
}

src/main/java/ddingdong/ddingdongBE/domain/documents/controller/AdminDocumentController.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static ddingdong.ddingdongBE.domain.fileinformation.entity.FileDomainCategory.DOCUMENT;
44
import static ddingdong.ddingdongBE.domain.fileinformation.entity.FileTypeCategory.FILE;
55

6+
import ddingdong.ddingdongBE.auth.PrincipalDetails;
67
import ddingdong.ddingdongBE.domain.documents.api.AdminDocumentApi;
78
import ddingdong.ddingdongBE.domain.documents.controller.dto.request.GenerateDocumentRequest;
89
import ddingdong.ddingdongBE.domain.documents.controller.dto.request.ModifyDocumentRequest;
@@ -11,10 +12,12 @@
1112
import ddingdong.ddingdongBE.domain.documents.entity.Document;
1213
import ddingdong.ddingdongBE.domain.documents.service.DocumentService;
1314
import ddingdong.ddingdongBE.domain.fileinformation.service.FileInformationService;
15+
import ddingdong.ddingdongBE.domain.user.entity.User;
1416
import ddingdong.ddingdongBE.file.dto.FileResponse;
1517
import ddingdong.ddingdongBE.file.service.FileService;
1618
import java.util.List;
1719
import lombok.RequiredArgsConstructor;
20+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1821
import org.springframework.web.bind.annotation.ModelAttribute;
1922
import org.springframework.web.bind.annotation.PathVariable;
2023
import org.springframework.web.bind.annotation.RequestPart;
@@ -29,9 +32,12 @@ public class AdminDocumentController implements AdminDocumentApi {
2932
private final FileService fileService;
3033
private final FileInformationService fileInformationService;
3134

32-
public void generateDocument(@ModelAttribute GenerateDocumentRequest generateDocumentRequest,
33-
@RequestPart(name = "uploadFiles") List<MultipartFile> uploadFiles) {
34-
Long createdDocumentId = documentService.create(generateDocumentRequest.toEntity());
35+
public void generateDocument(
36+
@AuthenticationPrincipal PrincipalDetails principalDetails,
37+
@ModelAttribute GenerateDocumentRequest generateDocumentRequest,
38+
@RequestPart(name = "uploadFiles") List<MultipartFile> uploadFiles) {
39+
User admin = principalDetails.getUser();
40+
Long createdDocumentId = documentService.create(generateDocumentRequest.toEntity(admin));
3541
fileService.uploadDownloadableFile(createdDocumentId, uploadFiles, FILE, DOCUMENT);
3642
}
3743

src/main/java/ddingdong/ddingdongBE/domain/documents/controller/dto/request/GenerateDocumentRequest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package ddingdong.ddingdongBE.domain.documents.controller.dto.request;
22

33
import ddingdong.ddingdongBE.domain.documents.entity.Document;
4+
import ddingdong.ddingdongBE.domain.user.entity.User;
45
import io.swagger.v3.oas.annotations.media.Schema;
5-
import lombok.AllArgsConstructor;
66
import lombok.Builder;
7-
import lombok.Getter;
87

98
@Schema(
109
name = "GenerateDocumentRequest",
@@ -18,8 +17,9 @@ public record GenerateDocumentRequest(
1817
@Schema(description = "자료 내용", example = "내용")
1918
String content
2019
) {
21-
public Document toEntity() {
20+
public Document toEntity(User user) {
2221
return Document.builder()
22+
.user(user)
2323
.title(title)
2424
.content(content)
2525
.build();

src/main/java/ddingdong/ddingdongBE/domain/documents/entity/Document.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package ddingdong.ddingdongBE.domain.documents.entity;
22

33
import ddingdong.ddingdongBE.common.BaseEntity;
4+
import ddingdong.ddingdongBE.domain.user.entity.User;
45
import java.time.LocalDateTime;
56
import javax.persistence.Column;
67
import javax.persistence.Entity;
8+
import javax.persistence.FetchType;
79
import javax.persistence.GeneratedValue;
810
import javax.persistence.GenerationType;
911
import javax.persistence.Id;
12+
import javax.persistence.JoinColumn;
13+
import javax.persistence.ManyToOne;
1014
import lombok.AccessLevel;
1115
import lombok.Builder;
1216
import lombok.Getter;
@@ -21,15 +25,20 @@ public class Document extends BaseEntity {
2125
@GeneratedValue(strategy = GenerationType.IDENTITY)
2226
private Long id;
2327

28+
@ManyToOne(fetch = FetchType.LAZY)
29+
@JoinColumn(name = "user_id")
30+
private User user;
31+
2432
@Column(nullable = false)
2533
private String title;
2634

2735
@Column(nullable = false, length = 1024)
2836
private String content;
2937

3038
@Builder
31-
private Document(Long id, String title, String content, LocalDateTime createdAt) {
39+
private Document(Long id, User user, String title, String content, LocalDateTime createdAt) {
3240
this.id = id;
41+
this.user = user;
3342
this.title = title;
3443
this.content = content;
3544
super.setCreatedAt(createdAt);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package ddingdong.ddingdongBE.domain.question.api;
2+
3+
4+
import ddingdong.ddingdongBE.auth.PrincipalDetails;
5+
import ddingdong.ddingdongBE.domain.question.controller.dto.request.GenerateQuestionRequest;
6+
import ddingdong.ddingdongBE.domain.question.controller.dto.request.ModifyQuestionRequest;
7+
import ddingdong.ddingdongBE.domain.question.controller.dto.response.AdminQuestionResponse;
8+
import io.swagger.v3.oas.annotations.Operation;
9+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
11+
import java.util.List;
12+
import org.springframework.http.HttpStatus;
13+
import org.springframework.http.MediaType;
14+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
15+
import org.springframework.web.bind.annotation.DeleteMapping;
16+
import org.springframework.web.bind.annotation.GetMapping;
17+
import org.springframework.web.bind.annotation.ModelAttribute;
18+
import org.springframework.web.bind.annotation.PatchMapping;
19+
import org.springframework.web.bind.annotation.PathVariable;
20+
import org.springframework.web.bind.annotation.PostMapping;
21+
import org.springframework.web.bind.annotation.RequestMapping;
22+
import org.springframework.web.bind.annotation.ResponseStatus;
23+
24+
@Tag(name = "FAQ - Admin", description = "FAQ Admin API")
25+
@RequestMapping("/server/admin/questions")
26+
public interface AdminQuestionApi {
27+
28+
@Operation(summary = "어드민 FAQ 업로드 API")
29+
@PostMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
30+
@ResponseStatus(HttpStatus.CREATED)
31+
@SecurityRequirement(name = "AccessToken")
32+
void generateQuestion(
33+
@AuthenticationPrincipal PrincipalDetails principalDetails,
34+
@ModelAttribute GenerateQuestionRequest generateDocumentRequest);
35+
36+
@Operation(summary = "어드민 FAQ 목록 조회 API")
37+
@GetMapping
38+
@ResponseStatus(HttpStatus.OK)
39+
@SecurityRequirement(name = "AccessToken")
40+
List<AdminQuestionResponse> getAllQuestions();
41+
42+
@Operation(summary = "어드민 FAQ 수정 API")
43+
@PatchMapping(value = "/{questionId}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
44+
@ResponseStatus(HttpStatus.NO_CONTENT)
45+
@SecurityRequirement(name = "AccessToken")
46+
void modifyQuestion(@PathVariable Long questionId,
47+
@ModelAttribute ModifyQuestionRequest modifyQuestionRequest);
48+
49+
@Operation(summary = "어드민 FAQ 삭제 API")
50+
@DeleteMapping("/{questionId}")
51+
@ResponseStatus(HttpStatus.NO_CONTENT)
52+
@SecurityRequirement(name = "AccessToken")
53+
void deleteQuestion(@PathVariable Long questionId);
54+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package ddingdong.ddingdongBE.domain.question.api;
2+
3+
4+
import ddingdong.ddingdongBE.domain.question.controller.dto.response.QuestionResponse;
5+
import io.swagger.v3.oas.annotations.Operation;
6+
import io.swagger.v3.oas.annotations.tags.Tag;
7+
import java.util.List;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.ResponseStatus;
12+
13+
@Tag(name = "FAQ", description = "FAQ API")
14+
@RequestMapping("/server/questions")
15+
public interface QuestionApi {
16+
17+
@Operation(summary = "FAQ 목록 조회 API")
18+
@GetMapping
19+
@ResponseStatus(HttpStatus.OK)
20+
List<QuestionResponse> getAllQuestions();
21+
22+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package ddingdong.ddingdongBE.domain.question.controller;
2+
3+
import ddingdong.ddingdongBE.auth.PrincipalDetails;
4+
import ddingdong.ddingdongBE.domain.question.api.AdminQuestionApi;
5+
import ddingdong.ddingdongBE.domain.question.controller.dto.request.GenerateQuestionRequest;
6+
import ddingdong.ddingdongBE.domain.question.controller.dto.request.ModifyQuestionRequest;
7+
import ddingdong.ddingdongBE.domain.question.controller.dto.response.AdminQuestionResponse;
8+
import ddingdong.ddingdongBE.domain.question.service.QuestionService;
9+
import ddingdong.ddingdongBE.domain.user.entity.User;
10+
import java.util.List;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
@RestController
15+
@RequiredArgsConstructor
16+
public class AdminQuestionController implements AdminQuestionApi {
17+
18+
private final QuestionService questionService;
19+
20+
@Override
21+
public void generateQuestion(PrincipalDetails principalDetails, GenerateQuestionRequest generateDocumentRequest) {
22+
User admin = principalDetails.getUser();
23+
questionService.create(generateDocumentRequest.toEntity(admin));
24+
}
25+
26+
@Override
27+
public List<AdminQuestionResponse> getAllQuestions() {
28+
return questionService.getAll().stream()
29+
.map(AdminQuestionResponse::from)
30+
.toList();
31+
}
32+
33+
@Override
34+
public void modifyQuestion(Long questionId, ModifyQuestionRequest modifyQuestionRequest) {
35+
questionService.update(questionId, modifyQuestionRequest.toEntity());
36+
}
37+
38+
@Override
39+
public void deleteQuestion(Long questionId) {
40+
questionService.delete(questionId);
41+
}
42+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package ddingdong.ddingdongBE.domain.question.controller;
2+
3+
import ddingdong.ddingdongBE.domain.question.api.QuestionApi;
4+
import ddingdong.ddingdongBE.domain.question.controller.dto.response.QuestionResponse;
5+
import ddingdong.ddingdongBE.domain.question.service.QuestionService;
6+
import java.util.List;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.web.bind.annotation.RestController;
9+
10+
@RestController
11+
@RequiredArgsConstructor
12+
public class QuestionController implements QuestionApi {
13+
14+
private final QuestionService questionService;
15+
16+
@Override
17+
public List<QuestionResponse> getAllQuestions() {
18+
return questionService.getAll().stream()
19+
.map(QuestionResponse::from)
20+
.toList();
21+
}
22+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package ddingdong.ddingdongBE.domain.question.controller.dto.request;
2+
3+
import ddingdong.ddingdongBE.domain.question.entity.Question;
4+
import ddingdong.ddingdongBE.domain.user.entity.User;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.Builder;
7+
8+
@Schema(
9+
name = "GenerateQuestionRequest",
10+
description = "FAQ 질문 생성 요청"
11+
)
12+
@Builder
13+
public record GenerateQuestionRequest(
14+
@Schema(description = "FAQ 질문", example = "질문")
15+
String question,
16+
@Schema(description = "FAQ 답변", example = "답변")
17+
String reply
18+
) {
19+
20+
public Question toEntity(User user) {
21+
return Question.builder()
22+
.user(user)
23+
.question(this.question)
24+
.reply(this.reply).build();
25+
}
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package ddingdong.ddingdongBE.domain.question.controller.dto.request;
2+
3+
import ddingdong.ddingdongBE.domain.question.entity.Question;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.Builder;
6+
7+
@Schema(
8+
name = "ModifyQuestionRequest",
9+
description = "FAQ 질문 수정 요청"
10+
)
11+
@Builder
12+
public record ModifyQuestionRequest(
13+
@Schema(description = "자료 제목", example = "제목")
14+
String question,
15+
@Schema(description = "자료 내용", example = "내용")
16+
String reply
17+
) {
18+
19+
public Question toEntity() {
20+
return Question.builder()
21+
.question(this.question)
22+
.reply(this.reply)
23+
.build();
24+
}
25+
}

0 commit comments

Comments
 (0)