Skip to content

Commit 098dc1d

Browse files
committed
✨ 퀴즈 조회, 수정, 삭제 API 구현
1 parent e1bc7b6 commit 098dc1d

File tree

8 files changed

+191
-2
lines changed

8 files changed

+191
-2
lines changed

backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,25 @@
44
import io.f1.backend.domain.quiz.dto.QuizCreateRequest;
55
import io.f1.backend.domain.quiz.dto.QuizCreateResponse;
66

7+
import io.f1.backend.domain.quiz.dto.QuizListPageResponse;
8+
import io.f1.backend.domain.quiz.dto.QuizUpdateRequest;
79
import jakarta.validation.Valid;
810

911
import lombok.RequiredArgsConstructor;
1012

13+
import org.springframework.data.domain.PageRequest;
14+
import org.springframework.data.domain.Pageable;
1115
import org.springframework.http.HttpStatus;
1216
import org.springframework.http.MediaType;
1317
import org.springframework.http.ResponseEntity;
18+
import org.springframework.web.bind.annotation.DeleteMapping;
19+
import org.springframework.web.bind.annotation.GetMapping;
20+
import org.springframework.web.bind.annotation.PathVariable;
1421
import org.springframework.web.bind.annotation.PostMapping;
22+
import org.springframework.web.bind.annotation.PutMapping;
23+
import org.springframework.web.bind.annotation.RequestBody;
1524
import org.springframework.web.bind.annotation.RequestMapping;
25+
import org.springframework.web.bind.annotation.RequestParam;
1626
import org.springframework.web.bind.annotation.RequestPart;
1727
import org.springframework.web.bind.annotation.RestController;
1828
import org.springframework.web.multipart.MultipartFile;
@@ -35,4 +45,36 @@ public ResponseEntity<QuizCreateResponse> saveQuiz(
3545

3646
return ResponseEntity.status(HttpStatus.CREATED).body(response);
3747
}
48+
49+
@DeleteMapping("/{quizId}")
50+
public ResponseEntity<Void> deleteQuiz(@PathVariable Long quizId) {
51+
52+
quizService.deleteQuiz(quizId);
53+
return ResponseEntity.noContent().build();
54+
}
55+
56+
@PutMapping("/{quizId}")
57+
public ResponseEntity<Void> updateQuiz(
58+
@PathVariable Long quizId,
59+
@RequestPart(required = false) MultipartFile thumbnailFile,
60+
@RequestPart QuizUpdateRequest request)
61+
throws IOException {
62+
63+
quizService.updateQuiz(quizId, thumbnailFile, request);
64+
65+
return ResponseEntity.noContent().build();
66+
}
67+
68+
@GetMapping
69+
public ResponseEntity<QuizListPageResponse> getQuizzes(
70+
@RequestParam(defaultValue = "1") int page,
71+
@RequestParam(defaultValue = "10") int size,
72+
@RequestParam(required = false) String title,
73+
@RequestParam(required = false) String creator) {
74+
75+
Pageable pageable = PageRequest.of(page - 1, size);
76+
QuizListPageResponse quizzes = quizService.getQuizzes(title, creator, pageable);
77+
78+
return ResponseEntity.ok().body(quizzes);
79+
}
3880
}

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

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
package io.f1.backend.domain.quiz.app;
22

3+
import static io.f1.backend.domain.quiz.mapper.QuizMapper.pageQuizToPageQuizListResponse;
34
import static io.f1.backend.domain.quiz.mapper.QuizMapper.quizCreateRequestToQuiz;
45
import static io.f1.backend.domain.quiz.mapper.QuizMapper.quizToQuizCreateResponse;
6+
import static io.f1.backend.domain.quiz.mapper.QuizMapper.toQuizListPageResponse;
7+
import static java.nio.file.Files.deleteIfExists;
58

69
import io.f1.backend.domain.question.app.QuestionService;
710
import io.f1.backend.domain.question.dto.QuestionRequest;
811
import io.f1.backend.domain.quiz.dao.QuizRepository;
912
import io.f1.backend.domain.quiz.dto.QuizCreateRequest;
1013
import io.f1.backend.domain.quiz.dto.QuizCreateResponse;
14+
import io.f1.backend.domain.quiz.dto.QuizListPageResponse;
15+
import io.f1.backend.domain.quiz.dto.QuizListResponse;
16+
import io.f1.backend.domain.quiz.dto.QuizUpdateRequest;
1117
import io.f1.backend.domain.quiz.entity.Quiz;
1218
import io.f1.backend.domain.user.dao.UserRepository;
1319
import io.f1.backend.domain.user.entity.User;
1420

21+
import java.util.NoSuchElementException;
1522
import lombok.RequiredArgsConstructor;
1623

1724
import org.springframework.beans.factory.annotation.Value;
25+
import org.springframework.data.domain.Page;
26+
import org.springframework.data.domain.Pageable;
1827
import org.springframework.stereotype.Service;
1928
import org.springframework.transaction.annotation.Transactional;
2029
import org.springframework.web.multipart.MultipartFile;
@@ -91,4 +100,84 @@ private String convertToThumbnailPath(MultipartFile thumbnailFile) throws IOExce
91100
private String getExtension(String filename) {
92101
return filename.substring(filename.lastIndexOf(".") + 1);
93102
}
94-
}
103+
104+
@Transactional
105+
public void deleteQuiz(Long quizId) {
106+
107+
Quiz quiz = quizRepository.findById(quizId)
108+
.orElseThrow(() -> new NoSuchElementException("존재하지 않는 퀴즈입니다."));
109+
110+
if(1L != quiz.getCreator().getId()) {
111+
throw new RuntimeException("권한이 없습니다.");
112+
}
113+
114+
deleteOldThumbnailFileIfNeeded(quiz.getThumbnailUrl());
115+
quizRepository.deleteById(quizId);
116+
}
117+
118+
@Transactional
119+
public void updateQuiz(Long quizId, MultipartFile thumbnailFile, QuizUpdateRequest request)
120+
throws IOException {
121+
122+
Quiz quiz = quizRepository.findById(quizId)
123+
.orElseThrow(() -> new NoSuchElementException("존재하지 않는 퀴즈입니다."));
124+
125+
if(request.title() != null) {
126+
quiz.changeTitle(request.title());
127+
}
128+
129+
if(request.description() != null) {
130+
quiz.changeDescription(request.description());
131+
}
132+
133+
if(thumbnailFile !=null && !thumbnailFile.isEmpty()) {
134+
validateImageFile(thumbnailFile);
135+
String newThumbnailPath = convertToThumbnailPath(thumbnailFile);
136+
137+
deleteOldThumbnailFileIfNeeded(quiz.getThumbnailUrl());
138+
quiz.changeThumbnailUrl(newThumbnailPath);
139+
}
140+
}
141+
142+
private void deleteOldThumbnailFileIfNeeded(String oldFilename) {
143+
if(oldFilename.contains("default")) {
144+
return;
145+
}
146+
147+
// oldFilename : /images/thumbnail/123asd.jpg
148+
// filename : 123asd.jpg
149+
String filename = oldFilename.substring(oldFilename.lastIndexOf("/") + 1);
150+
Path filePath = Paths.get(uploadPath, filename).toAbsolutePath();
151+
152+
try {
153+
boolean deleted = deleteIfExists(filePath);
154+
if( deleted ) {
155+
System.out.println("기존 썸네일 삭제 완료 : " + filePath);
156+
} else {
157+
System.out.println("기존 썸네일 존재 X : " + filePath);
158+
}
159+
} catch (IOException e) {
160+
System.err.println("기존 썸네일 삭제 중 오류 : " + filePath);
161+
throw new RuntimeException(e);
162+
}
163+
}
164+
165+
@Transactional(readOnly=true)
166+
public QuizListPageResponse getQuizzes(String title, String creator, Pageable pageable) {
167+
168+
Page<Quiz> quizzes;
169+
170+
// 검색어가 있을 때
171+
if(title != null && !title.isBlank()) {
172+
quizzes = quizRepository.findQuizzesByTitleContaining(title, pageable);
173+
} else if(creator !=null && !creator.isBlank()) {
174+
quizzes = quizRepository.findQuizzesByCreator_NicknameContaining(creator, pageable);
175+
} else { // 검색어가 없을 때 혹은 빈 문자열일 때
176+
quizzes = quizRepository.findAll(pageable);
177+
}
178+
179+
Page<QuizListResponse> quizListResponses = pageQuizToPageQuizListResponse(quizzes);
180+
181+
return toQuizListPageResponse(quizListResponses);
182+
}
183+
}

backend/src/main/java/io/f1/backend/domain/quiz/dao/QuizRepository.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
import io.f1.backend.domain.quiz.entity.Quiz;
44

5+
import org.springframework.data.domain.Page;
6+
import org.springframework.data.domain.Pageable;
57
import org.springframework.data.jpa.repository.JpaRepository;
8+
import org.springframework.data.jpa.repository.Query;
69

7-
public interface QuizRepository extends JpaRepository<Quiz, Long> {}
10+
public interface QuizRepository extends JpaRepository<Quiz, Long> {
11+
12+
Page<Quiz> findQuizzesByTitleContaining(String title, Pageable pageable);
13+
Page<Quiz> findQuizzesByCreator_NicknameContaining(String creator, Pageable pageable);
14+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.f1.backend.domain.quiz.dto;
2+
3+
import java.util.List;
4+
5+
public record QuizListPageResponse(int totalPages, int currentPage, long totalElements, List<QuizListResponse> quiz) { }
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package io.f1.backend.domain.quiz.dto;
2+
3+
public record QuizListResponse(Long quizId, String title, String description, String creatorNickname, int numberOfQuestion, String thumbnailUrl) { }
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package io.f1.backend.domain.quiz.dto;
2+
3+
public record QuizUpdateRequest(String title, String description) {}

backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,16 @@ public Quiz(
6969
public void addQuestion(Question question) {
7070
this.questions.add(question);
7171
}
72+
73+
public void changeTitle(String title) {
74+
this.title = title;
75+
}
76+
77+
public void changeDescription(String description) {
78+
this.description = description;
79+
}
80+
81+
public void changeThumbnailUrl(String thumbnailUrl) {
82+
this.thumbnailUrl = thumbnailUrl;
83+
}
7284
}

backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22

33
import io.f1.backend.domain.quiz.dto.QuizCreateRequest;
44
import io.f1.backend.domain.quiz.dto.QuizCreateResponse;
5+
import io.f1.backend.domain.quiz.dto.QuizListPageResponse;
6+
import io.f1.backend.domain.quiz.dto.QuizListResponse;
57
import io.f1.backend.domain.quiz.entity.Quiz;
68
import io.f1.backend.domain.user.entity.User;
9+
import java.util.List;
10+
import org.springframework.data.domain.Page;
711

812
public class QuizMapper {
913

@@ -30,4 +34,28 @@ public static QuizCreateResponse quizToQuizCreateResponse(Quiz quiz) {
3034
quiz.getThumbnailUrl(),
3135
quiz.getCreator().getId());
3236
}
37+
38+
public static QuizListResponse quizToQuizListResponse(Quiz quiz) {
39+
return new QuizListResponse(
40+
quiz.getId(),
41+
quiz.getTitle(),
42+
quiz.getDescription(),
43+
quiz.getCreator().getNickname(),
44+
quiz.getQuestions().size(),
45+
quiz.getThumbnailUrl()
46+
);
47+
}
48+
49+
public static QuizListPageResponse toQuizListPageResponse(Page<QuizListResponse> quizzes) {
50+
return new QuizListPageResponse(
51+
quizzes.getTotalPages(),
52+
quizzes.getNumber() + 1,
53+
quizzes.getTotalElements(),
54+
quizzes.getContent()
55+
);
56+
}
57+
58+
public static Page<QuizListResponse> pageQuizToPageQuizListResponse(Page<Quiz> quizzes) {
59+
return quizzes.map(QuizMapper::quizToQuizListResponse);
60+
}
3361
}

0 commit comments

Comments
 (0)