From 2b9349e3a9b9b76011d342df8d4f31f867a226df Mon Sep 17 00:00:00 2001 From: silver-eunjoo Date: Sun, 13 Jul 2025 03:19:07 +0900 Subject: [PATCH 1/8] =?UTF-8?q?:sparkles:=20feat=20:=20=ED=80=B4=EC=A6=88?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.gitignore | 5 +- .../domain/question/app/QuestionService.java | 36 ++++++++ .../question/dao/QuestionRepository.java | 8 ++ .../question/dao/TextQuestionRepository.java | 8 ++ .../domain/question/dto/QuestionRequest.java | 18 ++++ .../domain/question/entity/Question.java | 12 +++ .../domain/question/entity/TextQuestion.java | 8 ++ .../question/mapper/QuestionMapper.java | 13 +++ .../question/mapper/TextQuestionMapper.java | 12 +++ .../domain/quiz/api/QuizController.java | 32 +++++++ .../backend/domain/quiz/app/QuizService.java | 90 +++++++++++++++++++ .../domain/quiz/dao/QuizRepository.java | 8 ++ .../domain/quiz/dto/QuizCreateRequest.java | 26 ++++++ .../domain/quiz/dto/QuizCreateResponse.java | 5 ++ .../f1/backend/domain/quiz/entity/Quiz.java | 21 +++++ .../domain/quiz/mapper/QuizMapper.java | 28 ++++++ .../domain/user/dao/UserRepository.java | 9 ++ .../f1/backend/domain/user/entity/User.java | 2 + .../f1/backend/global/config/WebConfig.java | 16 +++- backend/src/main/resources/application.yml | 11 ++- backend/src/main/resources/data.sql | 2 + 21 files changed, 367 insertions(+), 3 deletions(-) create mode 100644 backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java create mode 100644 backend/src/main/java/io/f1/backend/domain/question/dao/QuestionRepository.java create mode 100644 backend/src/main/java/io/f1/backend/domain/question/dao/TextQuestionRepository.java create mode 100644 backend/src/main/java/io/f1/backend/domain/question/dto/QuestionRequest.java create mode 100644 backend/src/main/java/io/f1/backend/domain/question/mapper/QuestionMapper.java create mode 100644 backend/src/main/java/io/f1/backend/domain/question/mapper/TextQuestionMapper.java create mode 100644 backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java create mode 100644 backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java create mode 100644 backend/src/main/java/io/f1/backend/domain/quiz/dao/QuizRepository.java create mode 100644 backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateRequest.java create mode 100644 backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateResponse.java create mode 100644 backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java create mode 100644 backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java create mode 100644 backend/src/main/resources/data.sql diff --git a/backend/.gitignore b/backend/.gitignore index e6bbcaae..7d8f1866 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -40,4 +40,7 @@ out/ .env ### .idea ### -.idea \ No newline at end of file +.idea + +### images/thumbnail ### +images/thumbnail/** \ No newline at end of file diff --git a/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java b/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java new file mode 100644 index 00000000..6c1b234c --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java @@ -0,0 +1,36 @@ +package io.f1.backend.domain.question.app; + +import io.f1.backend.domain.question.dao.QuestionRepository; +import io.f1.backend.domain.question.dao.TextQuestionRepository; +import io.f1.backend.domain.question.dto.QuestionRequest; +import io.f1.backend.domain.question.entity.Question; +import io.f1.backend.domain.question.entity.TextQuestion; +import io.f1.backend.domain.question.mapper.QuestionMapper; +import io.f1.backend.domain.question.mapper.TextQuestionMapper; +import io.f1.backend.domain.quiz.entity.Quiz; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class QuestionService { + + private final QuestionRepository questionRepository; + private final TextQuestionRepository textQuestionRepository; + + @Transactional + public void saveQuestion(Quiz quiz, QuestionRequest request) { + + Question question = QuestionMapper.questionRequestToQuestion(quiz, request); + quiz.addQuestion(question); + questionRepository.save(question); + + TextQuestion textQuestion = TextQuestionMapper.questionRequestToTextQuestion(question, request.getContent()); + textQuestionRepository.save(textQuestion); + question.addTextQuestion(textQuestion); + + } + + +} diff --git a/backend/src/main/java/io/f1/backend/domain/question/dao/QuestionRepository.java b/backend/src/main/java/io/f1/backend/domain/question/dao/QuestionRepository.java new file mode 100644 index 00000000..0412bffe --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/question/dao/QuestionRepository.java @@ -0,0 +1,8 @@ +package io.f1.backend.domain.question.dao; + +import io.f1.backend.domain.question.entity.Question; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface QuestionRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/io/f1/backend/domain/question/dao/TextQuestionRepository.java b/backend/src/main/java/io/f1/backend/domain/question/dao/TextQuestionRepository.java new file mode 100644 index 00000000..94e9a811 --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/question/dao/TextQuestionRepository.java @@ -0,0 +1,8 @@ +package io.f1.backend.domain.question.dao; + +import io.f1.backend.domain.question.entity.TextQuestion; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TextQuestionRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/io/f1/backend/domain/question/dto/QuestionRequest.java b/backend/src/main/java/io/f1/backend/domain/question/dto/QuestionRequest.java new file mode 100644 index 00000000..e1df2aef --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/question/dto/QuestionRequest.java @@ -0,0 +1,18 @@ +package io.f1.backend.domain.question.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access= AccessLevel.PROTECTED) +public class QuestionRequest { + + @NotBlank(message="문제를 입력해주세요.") + private String content; + + @NotBlank(message="정답을 입력해주세요.") + private String answer; + +} diff --git a/backend/src/main/java/io/f1/backend/domain/question/entity/Question.java b/backend/src/main/java/io/f1/backend/domain/question/entity/Question.java index cc3ad718..c7f64a43 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/entity/Question.java +++ b/backend/src/main/java/io/f1/backend/domain/question/entity/Question.java @@ -13,8 +13,11 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToOne; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; @Entity +@NoArgsConstructor(access= AccessLevel.PROTECTED) public class Question extends BaseEntity { @Id @@ -30,4 +33,13 @@ public class Question extends BaseEntity { @OneToOne(mappedBy = "question", cascade = CascadeType.REMOVE) private TextQuestion textQuestion; + public Question(Quiz quiz, String answer) { + this.quiz = quiz; + this.answer = answer; + } + + public void addTextQuestion(TextQuestion textQuestion) { + this.textQuestion = textQuestion; + } + } diff --git a/backend/src/main/java/io/f1/backend/domain/question/entity/TextQuestion.java b/backend/src/main/java/io/f1/backend/domain/question/entity/TextQuestion.java index e395e5d6..8b3893e7 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/entity/TextQuestion.java +++ b/backend/src/main/java/io/f1/backend/domain/question/entity/TextQuestion.java @@ -7,8 +7,11 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; @Entity +@NoArgsConstructor(access= AccessLevel.PROTECTED) public class TextQuestion { @Id @@ -21,4 +24,9 @@ public class TextQuestion { @Column(nullable = false) private String content; + + public TextQuestion(Question question, String content) { + this.question = question; + this.content = content; + } } diff --git a/backend/src/main/java/io/f1/backend/domain/question/mapper/QuestionMapper.java b/backend/src/main/java/io/f1/backend/domain/question/mapper/QuestionMapper.java new file mode 100644 index 00000000..7a65fb68 --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/question/mapper/QuestionMapper.java @@ -0,0 +1,13 @@ +package io.f1.backend.domain.question.mapper; + +import io.f1.backend.domain.question.dto.QuestionRequest; +import io.f1.backend.domain.question.entity.Question; +import io.f1.backend.domain.quiz.entity.Quiz; + +public class QuestionMapper { + + public static Question questionRequestToQuestion(Quiz quiz, QuestionRequest questionRequest) { + return new Question(quiz, questionRequest.getAnswer()); + } + +} diff --git a/backend/src/main/java/io/f1/backend/domain/question/mapper/TextQuestionMapper.java b/backend/src/main/java/io/f1/backend/domain/question/mapper/TextQuestionMapper.java new file mode 100644 index 00000000..9078821e --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/question/mapper/TextQuestionMapper.java @@ -0,0 +1,12 @@ +package io.f1.backend.domain.question.mapper; + +import io.f1.backend.domain.question.entity.Question; +import io.f1.backend.domain.question.entity.TextQuestion; + +public class TextQuestionMapper { + + public static TextQuestion questionRequestToTextQuestion(Question question, String content) { + return new TextQuestion(question, content); + } + +} diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java b/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java new file mode 100644 index 00000000..9d4b03ca --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java @@ -0,0 +1,32 @@ +package io.f1.backend.domain.quiz.api; + +import io.f1.backend.domain.quiz.app.QuizService; +import io.f1.backend.domain.quiz.dto.QuizCreateRequest; +import io.f1.backend.domain.quiz.dto.QuizCreateResponse; +import jakarta.validation.Valid; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/quizzes") +@RequiredArgsConstructor +public class QuizController { + + private final QuizService quizService; + + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity saveQuiz(@RequestPart(required = false) MultipartFile file, @Valid @RequestPart QuizCreateRequest request) throws IOException { + QuizCreateResponse response = quizService.saveQuiz(file, request); + + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } + +} diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java b/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java new file mode 100644 index 00000000..40ff95de --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java @@ -0,0 +1,90 @@ +package io.f1.backend.domain.quiz.app; + +import io.f1.backend.domain.question.app.QuestionService; +import io.f1.backend.domain.question.dto.QuestionRequest; +import io.f1.backend.domain.quiz.dao.QuizRepository; +import io.f1.backend.domain.quiz.dto.QuizCreateRequest; +import io.f1.backend.domain.quiz.dto.QuizCreateResponse; +import io.f1.backend.domain.quiz.entity.Quiz; +import io.f1.backend.domain.quiz.mapper.QuizMapper; +import io.f1.backend.domain.user.dao.UserRepository; +import io.f1.backend.domain.user.entity.User; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +@RequiredArgsConstructor +public class QuizService { + + @Value("${file.thumbnail-path}") + private String uploadPath; + + @Value("${file.default-thumbnail-url}") + private String defaultThumbnailPath; + + // TODO : 시큐리티 구현 이후 삭제해도 되는 의존성 주입 + private final UserRepository userRepository; + private final QuestionService questionService; + private final QuizRepository quizRepository; + + @Transactional + public QuizCreateResponse saveQuiz(MultipartFile file, QuizCreateRequest request) throws IOException { + String imgUrl = defaultThumbnailPath; + + if(file!=null && !file.isEmpty()) { + validateImageFile(file); + imgUrl = saveThumbnail(file); + } + + // TODO : 시큐리티 구현 이후 삭제 (data.sql로 초기 저장해둔 유저 get), 나중엔 현재 로그인한 유저의 아이디를 받아오도록 수정 + User user = userRepository.findById(1L).orElseThrow(RuntimeException::new); + + Quiz quiz = QuizMapper.quizCreateRequestToQuiz(request, imgUrl, user); + + Quiz savedQuiz = quizRepository.save(quiz); + + for(QuestionRequest qRequest : request.getQuestions()) { + questionService.saveQuestion(savedQuiz, qRequest); + } + + return QuizMapper.quizToQuizCreateResponse(savedQuiz); + } + + private void validateImageFile(MultipartFile file) { + + if(!file.getContentType().startsWith("image")) { + // TODO : 이후 커스텀 예외로 변경 + throw new IllegalArgumentException("이미지 파일을 업로드해주세요."); + } + + List allowedExt = List.of("jpg", "jpeg", "png", "webp"); + if(!allowedExt.contains(getExtension(file.getOriginalFilename()))) { + throw new IllegalArgumentException("지원하지 않는 확장자입니다."); + } + } + + private String saveThumbnail(MultipartFile file) throws IOException { + String originalFilename = file.getOriginalFilename(); + String ext = getExtension(originalFilename); + String savedFilename = UUID.randomUUID().toString() + "." + ext; + + Path savePath = Paths.get(uploadPath, savedFilename).toAbsolutePath(); + file.transferTo(savePath.toFile()); + + return "/images/thumbnail/" + savedFilename; + } + + private String getExtension(String filename) { + return filename.substring(filename.lastIndexOf(".") + 1); + } + +} diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/dao/QuizRepository.java b/backend/src/main/java/io/f1/backend/domain/quiz/dao/QuizRepository.java new file mode 100644 index 00000000..9957576b --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/quiz/dao/QuizRepository.java @@ -0,0 +1,8 @@ +package io.f1.backend.domain.quiz.dao; + +import io.f1.backend.domain.quiz.entity.Quiz; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface QuizRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateRequest.java b/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateRequest.java new file mode 100644 index 00000000..df970f8f --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateRequest.java @@ -0,0 +1,26 @@ +package io.f1.backend.domain.quiz.dto; + +import io.f1.backend.domain.question.dto.QuestionRequest; +import io.f1.backend.domain.quiz.entity.QuizType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access= AccessLevel.PROTECTED) +public class QuizCreateRequest { + + @NotBlank(message="퀴즈 제목을 설정해주세요.") + private String title; + @NotNull(message="퀴즈 종류를 선택해주세요.") + private QuizType quizType; + @NotBlank(message="퀴즈 설명을 적어주세요.") + private String description; + @Size(min = 10, max=80, message = "문제는 최소 10개, 최대 80개로 정해주세요.") + private List questions; + +} diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateResponse.java b/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateResponse.java new file mode 100644 index 00000000..e4a1b017 --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateResponse.java @@ -0,0 +1,5 @@ +package io.f1.backend.domain.quiz.dto; + +import io.f1.backend.domain.quiz.entity.QuizType; + +public record QuizCreateResponse(Long id, String title, QuizType quizType, String description, String thumbnailUrl, Long creatorId) {} \ No newline at end of file diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java index 5ca9b550..666f9ffe 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java @@ -18,8 +18,14 @@ import java.util.ArrayList; import java.util.List; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Entity +@Getter +@NoArgsConstructor(access= AccessLevel.PROTECTED) public class Quiz extends BaseEntity { @Id @@ -45,4 +51,19 @@ public class Quiz extends BaseEntity { @ManyToOne @JoinColumn(name = "creator_id") private User creator; + + @Builder + public Quiz(String title, String description, QuizType quizType, String thumbnailUrl, + User creator) { + this.title = title; + this.description = description; + this.quizType = quizType; + this.thumbnailUrl = thumbnailUrl; + this.creator = creator; + } + + public void addQuestion(Question question) { + this.questions.add(question); + } + } diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java b/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java new file mode 100644 index 00000000..88d29610 --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java @@ -0,0 +1,28 @@ +package io.f1.backend.domain.quiz.mapper; + +import io.f1.backend.domain.quiz.dto.QuizCreateRequest; +import io.f1.backend.domain.quiz.dto.QuizCreateResponse; +import io.f1.backend.domain.quiz.entity.Quiz; +import io.f1.backend.domain.user.entity.User; + +public class QuizMapper { + + // TODO : 이후 파라미터에서 user 삭제하기 + public static Quiz quizCreateRequestToQuiz(QuizCreateRequest quizCreateRequest, String imgUrl, User user) { + + return Quiz.builder() + .title(quizCreateRequest.getTitle()) + .description(quizCreateRequest.getDescription()) + .quizType(quizCreateRequest.getQuizType()) + .thumbnailUrl(imgUrl) + .creator(user) // TODO : 이후 creator에 들어갈 User은 현재 로그인 중인 유저를 가져오도록 변경 + .build(); + } + + public static QuizCreateResponse quizToQuizCreateResponse(Quiz quiz) { + // TODO : creatorId 넣어주는 부분에서 Getter를 안 쓰고, 현재 로그인한 유저의 id를 담는 식으로 바꿔도 될 듯 + return new QuizCreateResponse(quiz.getId(), quiz.getTitle(), quiz.getQuizType(), + quiz.getDescription(), quiz.getThumbnailUrl(), quiz.getCreator().getId()); + } + +} diff --git a/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java b/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java new file mode 100644 index 00000000..5d2a2e1b --- /dev/null +++ b/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java @@ -0,0 +1,9 @@ +package io.f1.backend.domain.user.dao; + +import io.f1.backend.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +// TODO : 퀴즈 생성을 위한 user 생성을 위해 임의로 만듦. +public interface UserRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/io/f1/backend/domain/user/entity/User.java b/backend/src/main/java/io/f1/backend/domain/user/entity/User.java index ec19a33d..ca251848 100644 --- a/backend/src/main/java/io/f1/backend/domain/user/entity/User.java +++ b/backend/src/main/java/io/f1/backend/domain/user/entity/User.java @@ -13,8 +13,10 @@ import jakarta.persistence.Table; import java.time.LocalDateTime; +import lombok.Getter; @Entity +@Getter @Table(name = "`user`") public class User extends BaseEntity { diff --git a/backend/src/main/java/io/f1/backend/global/config/WebConfig.java b/backend/src/main/java/io/f1/backend/global/config/WebConfig.java index 224c315d..8d093eec 100644 --- a/backend/src/main/java/io/f1/backend/global/config/WebConfig.java +++ b/backend/src/main/java/io/f1/backend/global/config/WebConfig.java @@ -1,3 +1,17 @@ package io.f1.backend.global.config; -public class WebConfig {} +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // 클라이언트가 /image/thumbnail/~~ 로 요청 + // 실제 서버 경로 images/thumbnail/ 에서 리소스 찾아서 응답 + registry.addResourceHandler("/images/thumbnail/**") + .addResourceLocations("file:images/thumbnail/"); + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 82beff8c..7a6d0fbd 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -2,6 +2,10 @@ spring: config: import: optional:file:.env[.properties] + sql: + init: + mode: always # 현재는 data.sql 에서 더미 유저 자동 추가를 위해 넣어뒀음. + datasource: driver-class-name: com.mysql.cj.jdbc.Driver url : ${DB_URL} @@ -14,10 +18,15 @@ spring: port: ${REDIS_PORT} jpa: + defer-datasource-initialization: true # 현재는 data.sql 에서 더미 유저 자동 추가를 위해 넣어뒀음. hibernate: ddl-auto: create properties: hibernate: show_sql: true - format_sql: true \ No newline at end of file + format_sql: true + +file: + thumbnail-path : images/thumbnail/ # 이후 배포 환경에서는 바꾸면 될 듯 + default-thumbnail-url: /images/thumbnail/default.png diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql new file mode 100644 index 00000000..015cab81 --- /dev/null +++ b/backend/src/main/resources/data.sql @@ -0,0 +1,2 @@ +INSERT INTO user (id, nickname, provider, provider_id, last_login) +VALUES (1, 'test-user', 'kakao', 'kakao-1234', NOW()); From 679ee070cdd91c8d7caa922192b162d9a71402e3 Mon Sep 17 00:00:00 2001 From: github-actions <> Date: Sat, 12 Jul 2025 18:54:46 +0000 Subject: [PATCH 2/8] =?UTF-8?q?chore:=20Java=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/question/app/QuestionService.java | 8 ++--- .../question/dao/QuestionRepository.java | 5 ++- .../question/dao/TextQuestionRepository.java | 5 ++- .../domain/question/dto/QuestionRequest.java | 8 ++--- .../domain/question/entity/Question.java | 17 ++++----- .../domain/question/entity/TextQuestion.java | 11 +++--- .../question/mapper/QuestionMapper.java | 1 - .../question/mapper/TextQuestionMapper.java | 1 - .../domain/quiz/api/QuizController.java | 12 +++++-- .../backend/domain/quiz/app/QuizService.java | 26 +++++++------- .../domain/quiz/dao/QuizRepository.java | 5 ++- .../domain/quiz/dto/QuizCreateRequest.java | 19 ++++++---- .../domain/quiz/dto/QuizCreateResponse.java | 8 ++++- .../f1/backend/domain/quiz/entity/Quiz.java | 36 ++++++++++--------- .../domain/quiz/mapper/QuizMapper.java | 25 +++++++------ .../domain/user/dao/UserRepository.java | 5 ++- .../f1/backend/domain/user/entity/User.java | 3 +- 17 files changed, 110 insertions(+), 85 deletions(-) diff --git a/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java b/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java index 6c1b234c..e95c6e33 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java +++ b/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java @@ -8,7 +8,9 @@ import io.f1.backend.domain.question.mapper.QuestionMapper; import io.f1.backend.domain.question.mapper.TextQuestionMapper; import io.f1.backend.domain.quiz.entity.Quiz; + import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,11 +28,9 @@ public void saveQuestion(Quiz quiz, QuestionRequest request) { quiz.addQuestion(question); questionRepository.save(question); - TextQuestion textQuestion = TextQuestionMapper.questionRequestToTextQuestion(question, request.getContent()); + TextQuestion textQuestion = + TextQuestionMapper.questionRequestToTextQuestion(question, request.getContent()); textQuestionRepository.save(textQuestion); question.addTextQuestion(textQuestion); - } - - } diff --git a/backend/src/main/java/io/f1/backend/domain/question/dao/QuestionRepository.java b/backend/src/main/java/io/f1/backend/domain/question/dao/QuestionRepository.java index 0412bffe..7ff72081 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/dao/QuestionRepository.java +++ b/backend/src/main/java/io/f1/backend/domain/question/dao/QuestionRepository.java @@ -1,8 +1,7 @@ package io.f1.backend.domain.question.dao; import io.f1.backend.domain.question.entity.Question; -import org.springframework.data.jpa.repository.JpaRepository; -public interface QuestionRepository extends JpaRepository { +import org.springframework.data.jpa.repository.JpaRepository; -} +public interface QuestionRepository extends JpaRepository {} diff --git a/backend/src/main/java/io/f1/backend/domain/question/dao/TextQuestionRepository.java b/backend/src/main/java/io/f1/backend/domain/question/dao/TextQuestionRepository.java index 94e9a811..39812de0 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/dao/TextQuestionRepository.java +++ b/backend/src/main/java/io/f1/backend/domain/question/dao/TextQuestionRepository.java @@ -1,8 +1,7 @@ package io.f1.backend.domain.question.dao; import io.f1.backend.domain.question.entity.TextQuestion; -import org.springframework.data.jpa.repository.JpaRepository; -public interface TextQuestionRepository extends JpaRepository { +import org.springframework.data.jpa.repository.JpaRepository; -} +public interface TextQuestionRepository extends JpaRepository {} diff --git a/backend/src/main/java/io/f1/backend/domain/question/dto/QuestionRequest.java b/backend/src/main/java/io/f1/backend/domain/question/dto/QuestionRequest.java index e1df2aef..9374a9b9 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/dto/QuestionRequest.java +++ b/backend/src/main/java/io/f1/backend/domain/question/dto/QuestionRequest.java @@ -1,18 +1,18 @@ package io.f1.backend.domain.question.dto; import jakarta.validation.constraints.NotBlank; + import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Getter -@NoArgsConstructor(access= AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class QuestionRequest { - @NotBlank(message="문제를 입력해주세요.") + @NotBlank(message = "문제를 입력해주세요.") private String content; - @NotBlank(message="정답을 입력해주세요.") + @NotBlank(message = "정답을 입력해주세요.") private String answer; - } diff --git a/backend/src/main/java/io/f1/backend/domain/question/entity/Question.java b/backend/src/main/java/io/f1/backend/domain/question/entity/Question.java index c7f64a43..1b769383 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/entity/Question.java +++ b/backend/src/main/java/io/f1/backend/domain/question/entity/Question.java @@ -13,11 +13,12 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToOne; + import lombok.AccessLevel; import lombok.NoArgsConstructor; @Entity -@NoArgsConstructor(access= AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Question extends BaseEntity { @Id @@ -33,13 +34,13 @@ public class Question extends BaseEntity { @OneToOne(mappedBy = "question", cascade = CascadeType.REMOVE) private TextQuestion textQuestion; - public Question(Quiz quiz, String answer) { - this.quiz = quiz; - this.answer = answer; - } - public void addTextQuestion(TextQuestion textQuestion) { - this.textQuestion = textQuestion; - } + public Question(Quiz quiz, String answer) { + this.quiz = quiz; + this.answer = answer; + } + public void addTextQuestion(TextQuestion textQuestion) { + this.textQuestion = textQuestion; + } } diff --git a/backend/src/main/java/io/f1/backend/domain/question/entity/TextQuestion.java b/backend/src/main/java/io/f1/backend/domain/question/entity/TextQuestion.java index 8b3893e7..61eed1c7 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/entity/TextQuestion.java +++ b/backend/src/main/java/io/f1/backend/domain/question/entity/TextQuestion.java @@ -7,11 +7,12 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; + import lombok.AccessLevel; import lombok.NoArgsConstructor; @Entity -@NoArgsConstructor(access= AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class TextQuestion { @Id @@ -25,8 +26,8 @@ public class TextQuestion { @Column(nullable = false) private String content; - public TextQuestion(Question question, String content) { - this.question = question; - this.content = content; - } + public TextQuestion(Question question, String content) { + this.question = question; + this.content = content; + } } diff --git a/backend/src/main/java/io/f1/backend/domain/question/mapper/QuestionMapper.java b/backend/src/main/java/io/f1/backend/domain/question/mapper/QuestionMapper.java index 7a65fb68..4228fe8f 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/mapper/QuestionMapper.java +++ b/backend/src/main/java/io/f1/backend/domain/question/mapper/QuestionMapper.java @@ -9,5 +9,4 @@ public class QuestionMapper { public static Question questionRequestToQuestion(Quiz quiz, QuestionRequest questionRequest) { return new Question(quiz, questionRequest.getAnswer()); } - } diff --git a/backend/src/main/java/io/f1/backend/domain/question/mapper/TextQuestionMapper.java b/backend/src/main/java/io/f1/backend/domain/question/mapper/TextQuestionMapper.java index 9078821e..97e42cab 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/mapper/TextQuestionMapper.java +++ b/backend/src/main/java/io/f1/backend/domain/question/mapper/TextQuestionMapper.java @@ -8,5 +8,4 @@ public class TextQuestionMapper { public static TextQuestion questionRequestToTextQuestion(Question question, String content) { return new TextQuestion(question, content); } - } diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java b/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java index 9d4b03ca..6859ba54 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java @@ -3,9 +3,11 @@ import io.f1.backend.domain.quiz.app.QuizService; import io.f1.backend.domain.quiz.dto.QuizCreateRequest; import io.f1.backend.domain.quiz.dto.QuizCreateResponse; + import jakarta.validation.Valid; -import java.io.IOException; + import lombok.RequiredArgsConstructor; + import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -15,6 +17,8 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; + @RestController @RequestMapping("/quizzes") @RequiredArgsConstructor @@ -23,10 +27,12 @@ public class QuizController { private final QuizService quizService; @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity saveQuiz(@RequestPart(required = false) MultipartFile file, @Valid @RequestPart QuizCreateRequest request) throws IOException { + public ResponseEntity saveQuiz( + @RequestPart(required = false) MultipartFile file, + @Valid @RequestPart QuizCreateRequest request) + throws IOException { QuizCreateResponse response = quizService.saveQuiz(file, request); return ResponseEntity.status(HttpStatus.CREATED).body(response); } - } diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java b/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java index 40ff95de..d62b95d9 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java @@ -9,18 +9,20 @@ import io.f1.backend.domain.quiz.mapper.QuizMapper; import io.f1.backend.domain.user.dao.UserRepository; import io.f1.backend.domain.user.entity.User; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Optional; -import java.util.UUID; + import lombok.RequiredArgsConstructor; + import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.UUID; + @Service @RequiredArgsConstructor public class QuizService { @@ -37,10 +39,11 @@ public class QuizService { private final QuizRepository quizRepository; @Transactional - public QuizCreateResponse saveQuiz(MultipartFile file, QuizCreateRequest request) throws IOException { + public QuizCreateResponse saveQuiz(MultipartFile file, QuizCreateRequest request) + throws IOException { String imgUrl = defaultThumbnailPath; - if(file!=null && !file.isEmpty()) { + if (file != null && !file.isEmpty()) { validateImageFile(file); imgUrl = saveThumbnail(file); } @@ -52,7 +55,7 @@ public QuizCreateResponse saveQuiz(MultipartFile file, QuizCreateRequest request Quiz savedQuiz = quizRepository.save(quiz); - for(QuestionRequest qRequest : request.getQuestions()) { + for (QuestionRequest qRequest : request.getQuestions()) { questionService.saveQuestion(savedQuiz, qRequest); } @@ -61,13 +64,13 @@ public QuizCreateResponse saveQuiz(MultipartFile file, QuizCreateRequest request private void validateImageFile(MultipartFile file) { - if(!file.getContentType().startsWith("image")) { + if (!file.getContentType().startsWith("image")) { // TODO : 이후 커스텀 예외로 변경 throw new IllegalArgumentException("이미지 파일을 업로드해주세요."); } List allowedExt = List.of("jpg", "jpeg", "png", "webp"); - if(!allowedExt.contains(getExtension(file.getOriginalFilename()))) { + if (!allowedExt.contains(getExtension(file.getOriginalFilename()))) { throw new IllegalArgumentException("지원하지 않는 확장자입니다."); } } @@ -86,5 +89,4 @@ private String saveThumbnail(MultipartFile file) throws IOException { private String getExtension(String filename) { return filename.substring(filename.lastIndexOf(".") + 1); } - } diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/dao/QuizRepository.java b/backend/src/main/java/io/f1/backend/domain/quiz/dao/QuizRepository.java index 9957576b..a88c98e7 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/dao/QuizRepository.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/dao/QuizRepository.java @@ -1,8 +1,7 @@ package io.f1.backend.domain.quiz.dao; import io.f1.backend.domain.quiz.entity.Quiz; -import org.springframework.data.jpa.repository.JpaRepository; -public interface QuizRepository extends JpaRepository { +import org.springframework.data.jpa.repository.JpaRepository; -} +public interface QuizRepository extends JpaRepository {} diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateRequest.java b/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateRequest.java index df970f8f..418477fb 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateRequest.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateRequest.java @@ -2,25 +2,30 @@ import io.f1.backend.domain.question.dto.QuestionRequest; import io.f1.backend.domain.quiz.entity.QuizType; + import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -import java.util.List; + import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.List; + @Getter -@NoArgsConstructor(access= AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class QuizCreateRequest { - @NotBlank(message="퀴즈 제목을 설정해주세요.") + @NotBlank(message = "퀴즈 제목을 설정해주세요.") private String title; - @NotNull(message="퀴즈 종류를 선택해주세요.") + + @NotNull(message = "퀴즈 종류를 선택해주세요.") private QuizType quizType; - @NotBlank(message="퀴즈 설명을 적어주세요.") + + @NotBlank(message = "퀴즈 설명을 적어주세요.") private String description; - @Size(min = 10, max=80, message = "문제는 최소 10개, 최대 80개로 정해주세요.") - private List questions; + @Size(min = 10, max = 80, message = "문제는 최소 10개, 최대 80개로 정해주세요.") + private List questions; } diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateResponse.java b/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateResponse.java index e4a1b017..eddf6dfc 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateResponse.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/dto/QuizCreateResponse.java @@ -2,4 +2,10 @@ import io.f1.backend.domain.quiz.entity.QuizType; -public record QuizCreateResponse(Long id, String title, QuizType quizType, String description, String thumbnailUrl, Long creatorId) {} \ No newline at end of file +public record QuizCreateResponse( + Long id, + String title, + QuizType quizType, + String description, + String thumbnailUrl, + Long creatorId) {} diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java index 666f9ffe..ad137a1e 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java @@ -16,16 +16,17 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; -import java.util.ArrayList; -import java.util.List; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter -@NoArgsConstructor(access= AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Quiz extends BaseEntity { @Id @@ -52,18 +53,21 @@ public class Quiz extends BaseEntity { @JoinColumn(name = "creator_id") private User creator; - @Builder - public Quiz(String title, String description, QuizType quizType, String thumbnailUrl, - User creator) { - this.title = title; - this.description = description; - this.quizType = quizType; - this.thumbnailUrl = thumbnailUrl; - this.creator = creator; - } - - public void addQuestion(Question question) { - this.questions.add(question); - } + @Builder + public Quiz( + String title, + String description, + QuizType quizType, + String thumbnailUrl, + User creator) { + this.title = title; + this.description = description; + this.quizType = quizType; + this.thumbnailUrl = thumbnailUrl; + this.creator = creator; + } + public void addQuestion(Question question) { + this.questions.add(question); + } } diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java b/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java index 88d29610..01735453 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java @@ -8,21 +8,26 @@ public class QuizMapper { // TODO : 이후 파라미터에서 user 삭제하기 - public static Quiz quizCreateRequestToQuiz(QuizCreateRequest quizCreateRequest, String imgUrl, User user) { + public static Quiz quizCreateRequestToQuiz( + QuizCreateRequest quizCreateRequest, String imgUrl, User user) { return Quiz.builder() - .title(quizCreateRequest.getTitle()) - .description(quizCreateRequest.getDescription()) - .quizType(quizCreateRequest.getQuizType()) - .thumbnailUrl(imgUrl) - .creator(user) // TODO : 이후 creator에 들어갈 User은 현재 로그인 중인 유저를 가져오도록 변경 - .build(); + .title(quizCreateRequest.getTitle()) + .description(quizCreateRequest.getDescription()) + .quizType(quizCreateRequest.getQuizType()) + .thumbnailUrl(imgUrl) + .creator(user) // TODO : 이후 creator에 들어갈 User은 현재 로그인 중인 유저를 가져오도록 변경 + .build(); } public static QuizCreateResponse quizToQuizCreateResponse(Quiz quiz) { // TODO : creatorId 넣어주는 부분에서 Getter를 안 쓰고, 현재 로그인한 유저의 id를 담는 식으로 바꿔도 될 듯 - return new QuizCreateResponse(quiz.getId(), quiz.getTitle(), quiz.getQuizType(), - quiz.getDescription(), quiz.getThumbnailUrl(), quiz.getCreator().getId()); + return new QuizCreateResponse( + quiz.getId(), + quiz.getTitle(), + quiz.getQuizType(), + quiz.getDescription(), + quiz.getThumbnailUrl(), + quiz.getCreator().getId()); } - } diff --git a/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java b/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java index 5d2a2e1b..71267cd3 100644 --- a/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java +++ b/backend/src/main/java/io/f1/backend/domain/user/dao/UserRepository.java @@ -1,9 +1,8 @@ package io.f1.backend.domain.user.dao; import io.f1.backend.domain.user.entity.User; + import org.springframework.data.jpa.repository.JpaRepository; // TODO : 퀴즈 생성을 위한 user 생성을 위해 임의로 만듦. -public interface UserRepository extends JpaRepository { - -} +public interface UserRepository extends JpaRepository {} diff --git a/backend/src/main/java/io/f1/backend/domain/user/entity/User.java b/backend/src/main/java/io/f1/backend/domain/user/entity/User.java index ca251848..d6daa611 100644 --- a/backend/src/main/java/io/f1/backend/domain/user/entity/User.java +++ b/backend/src/main/java/io/f1/backend/domain/user/entity/User.java @@ -12,9 +12,10 @@ import jakarta.persistence.OneToOne; import jakarta.persistence.Table; -import java.time.LocalDateTime; import lombok.Getter; +import java.time.LocalDateTime; + @Entity @Getter @Table(name = "`user`") From 3800aa876e862f66a71b847f5b04a25846b0113b Mon Sep 17 00:00:00 2001 From: silver-eunjoo Date: Sun, 13 Jul 2025 04:06:21 +0900 Subject: [PATCH 3/8] =?UTF-8?q?:wrench:=20chore=20:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20application.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/test/resources/application.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml index 384b0bcb..713e2b9c 100644 --- a/backend/src/test/resources/application.yml +++ b/backend/src/test/resources/application.yml @@ -2,4 +2,12 @@ spring: datasource: url: jdbc:h2:mem:testdb;MODE=MYSQL username: sa - password: \ No newline at end of file + password: + + sql: + init: + mode: never + +file: + thumbnail-path : images/thumbnail/ # 이후 배포 환경에서는 바꾸면 될 듯 + default-thumbnail-url: /images/thumbnail/default.png From 0c1e44cb6807be2161c996eff302469e11db5390 Mon Sep 17 00:00:00 2001 From: silver-eunjoo Date: Mon, 14 Jul 2025 11:25:13 +0900 Subject: [PATCH 4/8] =?UTF-8?q?:recycle:=20refactor=20:=20PR=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=20(=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EB=84=A4=EC=9D=B4=EB=B0=8D,=20static=20import,=20=EB=B9=8C?= =?UTF-8?q?=EB=8D=94=20=ED=8C=A8=ED=84=B4=20=EC=82=AD=EC=A0=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/question/app/QuestionService.java | 9 +++++---- .../io/f1/backend/domain/quiz/app/QuizService.java | 14 ++++++++------ .../io/f1/backend/domain/quiz/entity/Quiz.java | 1 - .../f1/backend/domain/quiz/mapper/QuizMapper.java | 14 +++++++------- 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java b/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java index e95c6e33..a84c859d 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java +++ b/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java @@ -1,12 +1,13 @@ package io.f1.backend.domain.question.app; +import static io.f1.backend.domain.question.mapper.QuestionMapper.questionRequestToQuestion; +import static io.f1.backend.domain.question.mapper.TextQuestionMapper.questionRequestToTextQuestion; + import io.f1.backend.domain.question.dao.QuestionRepository; import io.f1.backend.domain.question.dao.TextQuestionRepository; import io.f1.backend.domain.question.dto.QuestionRequest; import io.f1.backend.domain.question.entity.Question; import io.f1.backend.domain.question.entity.TextQuestion; -import io.f1.backend.domain.question.mapper.QuestionMapper; -import io.f1.backend.domain.question.mapper.TextQuestionMapper; import io.f1.backend.domain.quiz.entity.Quiz; import lombok.RequiredArgsConstructor; @@ -24,12 +25,12 @@ public class QuestionService { @Transactional public void saveQuestion(Quiz quiz, QuestionRequest request) { - Question question = QuestionMapper.questionRequestToQuestion(quiz, request); + Question question = questionRequestToQuestion(quiz, request); quiz.addQuestion(question); questionRepository.save(question); TextQuestion textQuestion = - TextQuestionMapper.questionRequestToTextQuestion(question, request.getContent()); + questionRequestToTextQuestion(question, request.getContent()); textQuestionRepository.save(textQuestion); question.addTextQuestion(textQuestion); } diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java b/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java index d62b95d9..037d97a9 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java @@ -1,12 +1,14 @@ package io.f1.backend.domain.quiz.app; +import static io.f1.backend.domain.quiz.mapper.QuizMapper.quizCreateRequestToQuiz; +import static io.f1.backend.domain.quiz.mapper.QuizMapper.quizToQuizCreateResponse; + import io.f1.backend.domain.question.app.QuestionService; import io.f1.backend.domain.question.dto.QuestionRequest; import io.f1.backend.domain.quiz.dao.QuizRepository; import io.f1.backend.domain.quiz.dto.QuizCreateRequest; import io.f1.backend.domain.quiz.dto.QuizCreateResponse; import io.f1.backend.domain.quiz.entity.Quiz; -import io.f1.backend.domain.quiz.mapper.QuizMapper; import io.f1.backend.domain.user.dao.UserRepository; import io.f1.backend.domain.user.entity.User; @@ -41,17 +43,17 @@ public class QuizService { @Transactional public QuizCreateResponse saveQuiz(MultipartFile file, QuizCreateRequest request) throws IOException { - String imgUrl = defaultThumbnailPath; + String thumbnailPath = defaultThumbnailPath; if (file != null && !file.isEmpty()) { validateImageFile(file); - imgUrl = saveThumbnail(file); + thumbnailPath = convertToThumbnailPath(file); } // TODO : 시큐리티 구현 이후 삭제 (data.sql로 초기 저장해둔 유저 get), 나중엔 현재 로그인한 유저의 아이디를 받아오도록 수정 User user = userRepository.findById(1L).orElseThrow(RuntimeException::new); - Quiz quiz = QuizMapper.quizCreateRequestToQuiz(request, imgUrl, user); + Quiz quiz = quizCreateRequestToQuiz(request, thumbnailPath, user); Quiz savedQuiz = quizRepository.save(quiz); @@ -59,7 +61,7 @@ public QuizCreateResponse saveQuiz(MultipartFile file, QuizCreateRequest request questionService.saveQuestion(savedQuiz, qRequest); } - return QuizMapper.quizToQuizCreateResponse(savedQuiz); + return quizToQuizCreateResponse(savedQuiz); } private void validateImageFile(MultipartFile file) { @@ -75,7 +77,7 @@ private void validateImageFile(MultipartFile file) { } } - private String saveThumbnail(MultipartFile file) throws IOException { + private String convertToThumbnailPath(MultipartFile file) throws IOException { String originalFilename = file.getOriginalFilename(); String ext = getExtension(originalFilename); String savedFilename = UUID.randomUUID().toString() + "." + ext; diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java index ad137a1e..a9ed286f 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java @@ -53,7 +53,6 @@ public class Quiz extends BaseEntity { @JoinColumn(name = "creator_id") private User creator; - @Builder public Quiz( String title, String description, diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java b/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java index 01735453..780681ae 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java @@ -11,13 +11,13 @@ public class QuizMapper { public static Quiz quizCreateRequestToQuiz( QuizCreateRequest quizCreateRequest, String imgUrl, User user) { - return Quiz.builder() - .title(quizCreateRequest.getTitle()) - .description(quizCreateRequest.getDescription()) - .quizType(quizCreateRequest.getQuizType()) - .thumbnailUrl(imgUrl) - .creator(user) // TODO : 이후 creator에 들어갈 User은 현재 로그인 중인 유저를 가져오도록 변경 - .build(); + return new Quiz( + quizCreateRequest.getTitle(), + quizCreateRequest.getDescription(), + quizCreateRequest.getQuizType(), + imgUrl, + user // TODO : 이후 creator에 들어갈 User은 현재 로그인 중인 유저를 가져오도록 변경 + ); } public static QuizCreateResponse quizToQuizCreateResponse(Quiz quiz) { From e2d396c1fe5ac2cb77a90ba06b9801cadaf879b2 Mon Sep 17 00:00:00 2001 From: github-actions <> Date: Mon, 14 Jul 2025 02:25:27 +0000 Subject: [PATCH 5/8] =?UTF-8?q?chore:=20Java=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/domain/question/app/QuestionService.java | 3 +-- .../java/io/f1/backend/domain/quiz/entity/Quiz.java | 1 - .../io/f1/backend/domain/quiz/mapper/QuizMapper.java | 12 ++++++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java b/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java index a84c859d..87a0e222 100644 --- a/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java +++ b/backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java @@ -29,8 +29,7 @@ public void saveQuestion(Quiz quiz, QuestionRequest request) { quiz.addQuestion(question); questionRepository.save(question); - TextQuestion textQuestion = - questionRequestToTextQuestion(question, request.getContent()); + TextQuestion textQuestion = questionRequestToTextQuestion(question, request.getContent()); textQuestionRepository.save(textQuestion); question.addTextQuestion(textQuestion); } diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java index a9ed286f..05f99673 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java @@ -17,7 +17,6 @@ import jakarta.persistence.OneToMany; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java b/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java index 780681ae..762c6ead 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/mapper/QuizMapper.java @@ -12,12 +12,12 @@ public static Quiz quizCreateRequestToQuiz( QuizCreateRequest quizCreateRequest, String imgUrl, User user) { return new Quiz( - quizCreateRequest.getTitle(), - quizCreateRequest.getDescription(), - quizCreateRequest.getQuizType(), - imgUrl, - user // TODO : 이후 creator에 들어갈 User은 현재 로그인 중인 유저를 가져오도록 변경 - ); + quizCreateRequest.getTitle(), + quizCreateRequest.getDescription(), + quizCreateRequest.getQuizType(), + imgUrl, + user // TODO : 이후 creator에 들어갈 User은 현재 로그인 중인 유저를 가져오도록 변경 + ); } public static QuizCreateResponse quizToQuizCreateResponse(Quiz quiz) { From 14b7a63040df7a8e5e4a11adf8f5874ef4c24538 Mon Sep 17 00:00:00 2001 From: silver-eunjoo Date: Mon, 14 Jul 2025 11:28:12 +0900 Subject: [PATCH 6/8] =?UTF-8?q?:lipstick:=20style=20:=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20thumbnailFile=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/quiz/api/QuizController.java | 4 ++-- .../backend/domain/quiz/app/QuizService.java | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java b/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java index 6859ba54..b25897c2 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java @@ -28,10 +28,10 @@ public class QuizController { @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity saveQuiz( - @RequestPart(required = false) MultipartFile file, + @RequestPart(required = false) MultipartFile thumbnailFile, @Valid @RequestPart QuizCreateRequest request) throws IOException { - QuizCreateResponse response = quizService.saveQuiz(file, request); + QuizCreateResponse response = quizService.saveQuiz(thumbnailFile, request); return ResponseEntity.status(HttpStatus.CREATED).body(response); } diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java b/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java index 037d97a9..ffe46c50 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/app/QuizService.java @@ -41,13 +41,13 @@ public class QuizService { private final QuizRepository quizRepository; @Transactional - public QuizCreateResponse saveQuiz(MultipartFile file, QuizCreateRequest request) + public QuizCreateResponse saveQuiz(MultipartFile thumbnailFile, QuizCreateRequest request) throws IOException { String thumbnailPath = defaultThumbnailPath; - if (file != null && !file.isEmpty()) { - validateImageFile(file); - thumbnailPath = convertToThumbnailPath(file); + if (thumbnailFile != null && !thumbnailFile.isEmpty()) { + validateImageFile(thumbnailFile); + thumbnailPath = convertToThumbnailPath(thumbnailFile); } // TODO : 시큐리티 구현 이후 삭제 (data.sql로 초기 저장해둔 유저 get), 나중엔 현재 로그인한 유저의 아이디를 받아오도록 수정 @@ -64,26 +64,26 @@ public QuizCreateResponse saveQuiz(MultipartFile file, QuizCreateRequest request return quizToQuizCreateResponse(savedQuiz); } - private void validateImageFile(MultipartFile file) { + private void validateImageFile(MultipartFile thumbnailFile) { - if (!file.getContentType().startsWith("image")) { + if (!thumbnailFile.getContentType().startsWith("image")) { // TODO : 이후 커스텀 예외로 변경 throw new IllegalArgumentException("이미지 파일을 업로드해주세요."); } List allowedExt = List.of("jpg", "jpeg", "png", "webp"); - if (!allowedExt.contains(getExtension(file.getOriginalFilename()))) { + if (!allowedExt.contains(getExtension(thumbnailFile.getOriginalFilename()))) { throw new IllegalArgumentException("지원하지 않는 확장자입니다."); } } - private String convertToThumbnailPath(MultipartFile file) throws IOException { - String originalFilename = file.getOriginalFilename(); + private String convertToThumbnailPath(MultipartFile thumbnailFile) throws IOException { + String originalFilename = thumbnailFile.getOriginalFilename(); String ext = getExtension(originalFilename); String savedFilename = UUID.randomUUID().toString() + "." + ext; Path savePath = Paths.get(uploadPath, savedFilename).toAbsolutePath(); - file.transferTo(savePath.toFile()); + thumbnailFile.transferTo(savePath.toFile()); return "/images/thumbnail/" + savedFilename; } From 8a1973f19553e6ff2ab09ac6575ba725a8ca9e49 Mon Sep 17 00:00:00 2001 From: silver-eunjoo Date: Mon, 14 Jul 2025 11:42:12 +0900 Subject: [PATCH 7/8] =?UTF-8?q?:bug:=20fix=20:=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20=EB=B0=8F=20=EA=B8=B0=EB=B3=B8=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EC=A0=91=EA=B7=BC=20=EC=9E=84=EC=8B=9C?= =?UTF-8?q?=EB=A1=9C=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java | 3 +-- .../src/main/java/io/f1/backend/domain/user/entity/User.java | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java index 0db1abb6..5b556e1d 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java @@ -27,8 +27,7 @@ @Getter @Setter // quizService의 퀴즈 조회 메서드 구현 시까지 임시 사용 @Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor public class Quiz extends BaseEntity { @Id diff --git a/backend/src/main/java/io/f1/backend/domain/user/entity/User.java b/backend/src/main/java/io/f1/backend/domain/user/entity/User.java index 489fe19f..e989134c 100644 --- a/backend/src/main/java/io/f1/backend/domain/user/entity/User.java +++ b/backend/src/main/java/io/f1/backend/domain/user/entity/User.java @@ -20,7 +20,6 @@ @Getter @Setter // quizService의 퀴즈 조회 메서드 구현 시까지 임시 사용 @Entity -@Getter @Table(name = "`user`") public class User extends BaseEntity { From b304eceab19a2859b398cbcb0bc0272cbb00ebf1 Mon Sep 17 00:00:00 2001 From: github-actions <> Date: Mon, 14 Jul 2025 02:42:25 +0000 Subject: [PATCH 8/8] =?UTF-8?q?chore:=20Java=20=EC=8A=A4=ED=83=80=EC=9D=BC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java index 5b556e1d..5168a1e1 100644 --- a/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java +++ b/backend/src/main/java/io/f1/backend/domain/quiz/entity/Quiz.java @@ -16,7 +16,6 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; -import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter;