Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2de3f9b
✨ (#363) 퀴즈 대시보드 대시보드 연결
iinuyha Nov 1, 2025
119ed40
✨ (#363) 퀴즈 정보 불러오기 api 연동
iinuyha Nov 1, 2025
723a3ad
✨ (#363) 제출 학생 명단 불러오기
iinuyha Nov 1, 2025
fb53c04
🔨 (#363) 데이터 패치 후 렌더링
iinuyha Nov 1, 2025
655c7cc
🐛 (#363) hydration error 해결
iinuyha Nov 1, 2025
bcf8849
✨ (#363) 퀴즈 상세 통계 데이터 연결
iinuyha Nov 1, 2025
3c3b3ad
:sparkles: (#365) 퀴즈 생성 로직 변경
mumminn Nov 11, 2025
57fb1a3
✨ (#365) 퀴즈 재생성 했을경우 재생성 버튼 안보이도록 수정
mumminn Nov 11, 2025
fb74d71
:bug: (#365) 생성 에러 수정
mumminn Nov 11, 2025
05957c9
💄 (#365) 퀴즈 관리 list top 패딩 추가
mumminn Nov 11, 2025
80547c2
✨ (#365) 퀴즈 목록 BackButtonHeader 추가
mumminn Nov 11, 2025
65bbd57
✨ (#365) 퀴즈 문제 컴포넌트 추가
mumminn Nov 11, 2025
6c67f3c
✨ (#365) 퀴즈 정보 컴포넌트 추가
mumminn Nov 11, 2025
f7d8b0f
✨ (#365) 퀴즈 리스트 컨테이너, 페이지 추가
mumminn Nov 11, 2025
1703cd4
✨ (#365) 퀴즈 문제 확인 라우터 설정
mumminn Nov 11, 2025
eba7527
✨ (#365) 12시 전에 퀴즈 문제 확인 상태 변경
mumminn Nov 11, 2025
c5a25f2
✨ (#365) 퀴즈 정보 API 연동
mumminn Nov 11, 2025
4f6113f
🔥 (#365) 문제: 두번 들어가 있는 거 삭제
mumminn Nov 11, 2025
c8093f2
♻️ (#365) fetchQuizList 응답 타입에 맞게 QuizSection 타입 수정
mumminn Nov 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,41 @@ public class QuizController {
private final QuizService quizService;
private final QuizEditService quizEditService;

// 퀴즈 생성
@PostMapping("/{lectureId}/create")
public ResponseEntity<ApiResponse<QuizResponseDTO>> generateQuiz(@PathVariable("lectureId") UUID lectureId,
@RequestBody QuizRequestDTO request) {
try {
QuizResponseDTO response = quizService.generateQuiz(lectureId, request, false);
return ResponseEntity.ok(ApiResponse.onSuccess(response));
} catch (QuizException e) {
return ResponseEntity
.status(e.getErrorCode().getReasonHttpStatus().getHttpStatus())
.body(ApiResponse.onFailure(e.getErrorCode()));
} catch (Exception e) {
return ResponseEntity
.status(FailureCode._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getHttpStatus())
.body(ApiResponse.onFailure(FailureCode._INTERNAL_SERVER_ERROR));
}
}

// 퀴즈 재생성
@PostMapping("/{lectureId}/re-create")
public ResponseEntity<ApiResponse<QuizResponseDTO>> regenerateQuiz(@PathVariable("lectureId") UUID lectureId,
@RequestBody QuizRequestDTO request) {
try {
QuizResponseDTO response = quizService.generateQuiz(lectureId, request, true);
return ResponseEntity.ok(ApiResponse.onSuccess(response));
} catch (QuizException e) {
return ResponseEntity
.status(e.getErrorCode().getReasonHttpStatus().getHttpStatus())
.body(ApiResponse.onFailure(e.getErrorCode()));
} catch (Exception e) {
return ResponseEntity
.status(FailureCode._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getHttpStatus())
.body(ApiResponse.onFailure(FailureCode._INTERNAL_SERVER_ERROR));
}
}
// // 퀴즈 생성
// @PostMapping("/{lectureId}/create")
// public ResponseEntity<ApiResponse<QuizResponseDTO>> generateQuiz(@PathVariable("lectureId") UUID lectureId,
// @RequestBody QuizRequestDTO request) {
// try {
// QuizResponseDTO response = quizService.generateQuiz(lectureId, request, false);
// return ResponseEntity.ok(ApiResponse.onSuccess(response));
// } catch (QuizException e) {
// return ResponseEntity
// .status(e.getErrorCode().getReasonHttpStatus().getHttpStatus())
// .body(ApiResponse.onFailure(e.getErrorCode()));
// } catch (Exception e) {
// return ResponseEntity
// .status(FailureCode._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getHttpStatus())
// .body(ApiResponse.onFailure(FailureCode._INTERNAL_SERVER_ERROR));
// }
// }
//
// // 퀴즈 재생성
// @PostMapping("/{lectureId}/re-create")
// public ResponseEntity<ApiResponse<QuizResponseDTO>> regenerateQuiz(@PathVariable("lectureId") UUID lectureId,
// @RequestBody QuizRequestDTO request) {
// try {
// QuizResponseDTO response = quizService.generateQuiz(lectureId, request, true);
// return ResponseEntity.ok(ApiResponse.onSuccess(response));
// } catch (QuizException e) {
// return ResponseEntity
// .status(e.getErrorCode().getReasonHttpStatus().getHttpStatus())
// .body(ApiResponse.onFailure(e.getErrorCode()));
// } catch (Exception e) {
// return ResponseEntity
// .status(FailureCode._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getHttpStatus())
// .body(ApiResponse.onFailure(FailureCode._INTERNAL_SERVER_ERROR));
// }
// }

// 퀴즈 저장
@PostMapping("/{lectureId}/save")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.example.backend.domain.quiz.exception.QuizException;
import org.example.backend.domain.quiz.repository.QuizRepository;
import org.example.backend.domain.quizAnswer.repository.QuizAnswerRepository;
import org.example.backend.domain.quizList.repository.QuizListRepository;
import org.example.backend.domain.user.entity.Role;
import org.example.backend.global.security.auth.CustomSecurityUtil;
import org.example.backend.infra.langchain.LangChainClient;
Expand Down Expand Up @@ -56,6 +57,7 @@ public class QuizServiceImpl implements QuizService {
private final QuizAnswerRepository quizAnswerRepository;
private final CustomSecurityUtil customSecurityUtil;
private final QuizConverter quizConverter;
private final QuizListRepository quizListRepository;

private final TaskScheduler taskScheduler;
private final NotificationService notificationService;
Expand Down Expand Up @@ -129,6 +131,7 @@ public QuizResponseDTO generateQuiz(UUID lectureId, QuizRequestDTO request, bool

// 퀴즈 저장
@Override
@Transactional
public QuizSaveResponseDTO saveQuiz(UUID lectureId, QuizSaveRequestDTO request) {

Role role = customSecurityUtil.getUserRole();
Expand Down Expand Up @@ -183,13 +186,15 @@ public QuizSaveResponseDTO saveQuiz(UUID lectureId, QuizSaveRequestDTO request)
}
scheduleQuizAnswerUploadNotification(lecture);

quizListRepository.resetAllUsedFlags();

return QuizSaveResponseDTO.builder()
.lectureId(lectureId)
.savedCount(savedQuizIds.size())
.quizIds(savedQuizIds)
.build();
}

private void scheduleQuizAnswerUploadNotification(Lecture lecture) {
// 현재 시간 기준으로 "오늘 밤 12시(자정)" 계산
LocalDateTime midnight = LocalDate.now()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.example.backend.domain.quizList.controller;

import lombok.RequiredArgsConstructor;
import org.example.backend.domain.quiz.dto.response.QuizResponseDTO;
import org.example.backend.domain.quiz.exception.QuizException;
import org.example.backend.domain.quizList.service.QuizListService;
import org.example.backend.global.ApiResponse;
import org.example.backend.global.code.base.FailureCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@RequestMapping("/api/quizzes")
@RequiredArgsConstructor
public class QuizListController {

private final QuizListService quizListService;

@PostMapping("/{lectureId}/create")
public ResponseEntity<ApiResponse<QuizResponseDTO>> createRandomQuiz(
@PathVariable("lectureId") UUID lectureId) {
try {
QuizResponseDTO response = quizListService.createRandomQuizSet(lectureId);
return ResponseEntity.ok(ApiResponse.onSuccess(response));
} catch (QuizException e) {
return ResponseEntity
.status(e.getErrorCode().getReasonHttpStatus().getHttpStatus())
.body(ApiResponse.onFailure(e.getErrorCode()));
} catch (Exception e) {
return ResponseEntity
.status(FailureCode._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getHttpStatus())
.body(ApiResponse.onFailure(FailureCode._INTERNAL_SERVER_ERROR));
}
}

@PostMapping("/{lectureId}/re-create")
public ResponseEntity<ApiResponse<QuizResponseDTO>> recreateRandomQuiz(
@PathVariable("lectureId") UUID lectureId) {
try {
QuizResponseDTO response = quizListService.recreateRandomQuizSet(lectureId);
return ResponseEntity.ok(ApiResponse.onSuccess(response));
} catch (QuizException e) {
return ResponseEntity
.status(e.getErrorCode().getReasonHttpStatus().getHttpStatus())
.body(ApiResponse.onFailure(e.getErrorCode()));
} catch (Exception e) {
return ResponseEntity
.status(FailureCode._INTERNAL_SERVER_ERROR.getReasonHttpStatus().getHttpStatus())
.body(ApiResponse.onFailure(FailureCode._INTERNAL_SERVER_ERROR));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.example.backend.domain.quizList.entity;

import jakarta.persistence.*;
import lombok.*;
import org.example.backend.domain.lecture.entity.Lecture;
import org.example.backend.domain.quiz.entity.QuizType;
import org.example.backend.domain.quizListOptions.entity.QuizListOptions;
import org.example.backend.global.entitiy.BaseEntity;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Entity
@Table(name = "quiz_list")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class QuizList extends BaseEntity {

@Id
@GeneratedValue
@Column(columnDefinition = "BINARY(16)")
private UUID id;

@ManyToOne
@JoinColumn(name = "lecture_id", nullable = false)
private Lecture lecture;

@Column(nullable = false)
private String quiz;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private QuizType type;

@Column(nullable = false)
private String solution;

@Column(name = "quiz_order", nullable = false)
private Integer quizOrder;

@Column(name = "used", length = 1, nullable = false, columnDefinition = "CHAR(1) DEFAULT 'F'")
private String used;

@OneToMany(mappedBy = "quizListId", cascade = CascadeType.ALL, orphanRemoval = true)
private List<QuizListOptions> quizListOptions = new ArrayList<>();

public void update(String quizBody, String solution, QuizType type) {
this.quiz = quizBody;
this.solution = solution;
this.type = type;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.example.backend.domain.quizList.repository;

import org.example.backend.domain.quizList.entity.QuizList;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.UUID;

@Repository
public interface QuizListRepository extends JpaRepository<QuizList, UUID> {

@Query(value = """
SELECT * FROM quiz_list
WHERE lecture_id = :lectureId
AND type = :type
AND used = 'F'
ORDER BY RAND()
LIMIT :limit
""", nativeQuery = true)
List<QuizList> findRandomByLectureIdAndType(
@Param("lectureId") UUID lectureId,
@Param("type") String type,
@Param("limit") int limit);

@Query(value = """
SELECT * FROM quiz_list
WHERE lecture_id = :lectureId
AND type = :type
AND used != 'T'
ORDER BY RAND()
LIMIT :limit
""", nativeQuery = true)
List<QuizList> findRandomByLectureIdAndTypeExcludeUsed(
@Param("lectureId") UUID lectureId,
@Param("type") String type,
@Param("limit") int limit);

@Modifying
@Query(value = "UPDATE quiz_list SET used = 'F'", nativeQuery = true)
void resetAllUsedFlags();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.example.backend.domain.quizList.service;


import org.example.backend.domain.quiz.dto.response.QuizResponseDTO;

import java.util.UUID;

public interface QuizListService {
QuizResponseDTO createRandomQuizSet(UUID lectureId);
QuizResponseDTO recreateRandomQuizSet(UUID lectureId);
}
Loading