Skip to content

Commit 3f1fca6

Browse files
committed
✨ feat : 문제 수정, 삭제 API 추가 + TrimmedSize 유효성 검사 어노테이션 정의
1 parent 0ba0181 commit 3f1fca6

File tree

11 files changed

+160
-1
lines changed

11 files changed

+160
-1
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.f1.backend.domain.question.api;
2+
3+
import io.f1.backend.domain.question.app.QuestionService;
4+
import io.f1.backend.domain.question.dto.QuestionUpdateRequest;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.DeleteMapping;
8+
import org.springframework.web.bind.annotation.PathVariable;
9+
import org.springframework.web.bind.annotation.PutMapping;
10+
import org.springframework.web.bind.annotation.RequestBody;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
@RestController
15+
@RequestMapping("/questions")
16+
@RequiredArgsConstructor
17+
public class QuestionController {
18+
19+
private final QuestionService questionService;
20+
21+
@PutMapping("/{questionId}")
22+
public ResponseEntity<Void> updateQuestion(@PathVariable Long questionId, @RequestBody QuestionUpdateRequest request) {
23+
questionService.updateQuestion(questionId, request);
24+
25+
return ResponseEntity.noContent().build();
26+
}
27+
28+
@DeleteMapping("/{questionId}")
29+
public ResponseEntity<Void> deleteQuestion(@PathVariable Long questionId) {
30+
questionService.deleteQuestion(questionId);
31+
32+
return ResponseEntity.noContent().build();
33+
}
34+
35+
}

backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
import io.f1.backend.domain.question.dao.QuestionRepository;
77
import io.f1.backend.domain.question.dao.TextQuestionRepository;
88
import io.f1.backend.domain.question.dto.QuestionRequest;
9+
import io.f1.backend.domain.question.dto.QuestionUpdateRequest;
910
import io.f1.backend.domain.question.entity.Question;
1011
import io.f1.backend.domain.question.entity.TextQuestion;
1112
import io.f1.backend.domain.quiz.entity.Quiz;
1213

14+
import java.util.NoSuchElementException;
1315
import lombok.RequiredArgsConstructor;
1416

1517
import org.springframework.stereotype.Service;
@@ -33,4 +35,46 @@ public void saveQuestion(Quiz quiz, QuestionRequest request) {
3335
textQuestionRepository.save(textQuestion);
3436
question.addTextQuestion(textQuestion);
3537
}
38+
39+
@Transactional
40+
public void updateQuestion(Long questionId, QuestionUpdateRequest request) {
41+
42+
Question question = questionRepository.findById(questionId)
43+
.orElseThrow(() -> new NoSuchElementException("존재하지 않는 문제입니다."));
44+
45+
TextQuestion textQuestion = question.getTextQuestion();
46+
47+
if(request.content() != null) {
48+
validateContent(request.content());
49+
textQuestion.changeContent(request.content());
50+
}
51+
52+
if(request.answer() != null) {
53+
validateAnswer(request.answer());
54+
question.changeAnswer(request.answer());
55+
}
56+
57+
}
58+
59+
@Transactional
60+
public void deleteQuestion(Long questionId) {
61+
62+
Question question = questionRepository.findById(questionId)
63+
.orElseThrow(() -> new NoSuchElementException("존재하지 않는 문제입니다."));
64+
65+
questionRepository.delete(question);
66+
67+
}
68+
69+
private void validateAnswer(String answer) {
70+
if(answer.trim().length() < 5 || answer.trim().length() > 30) {
71+
throw new IllegalArgumentException("정답은 1자 이상 30자 이하로 입력해주세요.");
72+
}
73+
}
74+
75+
private void validateContent(String content) {
76+
if(content.trim().length() < 5 || content.trim().length() > 30) {
77+
throw new IllegalArgumentException("문제는 5자 이상 30자 이하로 입력해주세요.");
78+
}
79+
}
3680
}

backend/src/main/java/io/f1/backend/domain/question/dto/QuestionRequest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.f1.backend.domain.question.dto;
22

3+
import io.f1.backend.global.validation.TrimmedSize;
34
import jakarta.validation.constraints.NotBlank;
45

56
import lombok.AccessLevel;
@@ -10,9 +11,11 @@
1011
@NoArgsConstructor(access = AccessLevel.PROTECTED)
1112
public class QuestionRequest {
1213

14+
@TrimmedSize(min=5, max=30)
1315
@NotBlank(message = "문제를 입력해주세요.")
1416
private String content;
1517

18+
@TrimmedSize(min=1, max=30)
1619
@NotBlank(message = "정답을 입력해주세요.")
1720
private String answer;
1821
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.f1.backend.domain.question.dto;
2+
3+
public record QuestionUpdateRequest(String content, String answer) {
4+
5+
}

backend/src/main/java/io/f1/backend/domain/question/entity/Question.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,8 @@ public Question(Quiz quiz, String answer) {
4545
public void addTextQuestion(TextQuestion textQuestion) {
4646
this.textQuestion = textQuestion;
4747
}
48+
49+
public void changeAnswer(String answer) {
50+
this.answer = answer;
51+
}
4852
}

backend/src/main/java/io/f1/backend/domain/question/entity/TextQuestion.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@ public TextQuestion(Question question, String content) {
3232
this.question = question;
3333
this.content = content;
3434
}
35+
36+
public void changeContent(String content) {
37+
this.content = content;
38+
}
3539
}

backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,12 @@ public void updateQuiz(Long quizId, MultipartFile thumbnailFile, QuizUpdateReque
132132
.orElseThrow(() -> new NoSuchElementException("존재하지 않는 퀴즈입니다."));
133133

134134
if (request.title() != null) {
135+
validateTitle(request.title());
135136
quiz.changeTitle(request.title());
136137
}
137138

138139
if (request.description() != null) {
140+
validateDesc(request.description());
139141
quiz.changeDescription(request.description());
140142
}
141143

@@ -148,6 +150,18 @@ public void updateQuiz(Long quizId, MultipartFile thumbnailFile, QuizUpdateReque
148150
}
149151
}
150152

153+
private void validateDesc(String desc) {
154+
if( desc.trim().length() < 10 || desc.trim().length() > 50) {
155+
throw new IllegalArgumentException("설명은 10자 이상 50자 이하로 입력해주세요.");
156+
}
157+
}
158+
159+
private void validateTitle(String title) {
160+
if (title.trim().length() < 2 || title.trim().length() > 30) {
161+
throw new IllegalArgumentException("제목은 2자 이상 30자 이하로 입력해주세요.");
162+
}
163+
}
164+
151165
private void deleteThumbnailFile(String oldFilename) {
152166
if (oldFilename.contains(DEFAULT)) {
153167
return;

backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateRequest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.f1.backend.domain.question.dto.QuestionRequest;
44
import io.f1.backend.domain.quiz.entity.QuizType;
55

6+
import io.f1.backend.global.validation.TrimmedSize;
67
import jakarta.validation.constraints.NotBlank;
78
import jakarta.validation.constraints.NotNull;
89
import jakarta.validation.constraints.Size;
@@ -17,12 +18,14 @@
1718
@NoArgsConstructor(access = AccessLevel.PROTECTED)
1819
public class QuizCreateRequest {
1920

21+
@TrimmedSize(min = 2, max = 30)
2022
@NotBlank(message = "퀴즈 제목을 설정해주세요.")
2123
private String title;
2224

2325
@NotNull(message = "퀴즈 종류를 선택해주세요.")
2426
private QuizType quizType;
2527

28+
@TrimmedSize(min = 10, max = 50)
2629
@NotBlank(message = "퀴즈 설명을 적어주세요.")
2730
private String description;
2831

backend/src/main/java/io/f1/backend/global/config/SecurityConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public SecurityFilterChain userFilterChain(HttpSecurity http) throws Exception {
3838
"/oauth2/**",
3939
"/signup",
4040
"/css/**",
41-
"/js/**")
41+
"/js/**", "/**")
4242
.permitAll()
4343
.requestMatchers("/ws/**")
4444
.authenticated()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.f1.backend.global.validation;
2+
3+
import jakarta.validation.Constraint;
4+
import jakarta.validation.Payload;
5+
import java.lang.annotation.*;
6+
7+
@Documented
8+
@Constraint(validatedBy = TrimmedSizeValidator.class)
9+
@Target({ ElementType.FIELD, ElementType.PARAMETER })
10+
@Retention(RetentionPolicy.RUNTIME)
11+
public @interface TrimmedSize {
12+
13+
String message() default "공백 제외 길이가 {min}자 이상 {min}자 이하여야 합니다.";
14+
15+
int min() default 0;
16+
int max() default 50;
17+
18+
Class<?>[] groups() default {};
19+
Class<? extends Payload>[] payload() default {};
20+
21+
}

0 commit comments

Comments
 (0)