16 changed files with 138 additions and 3 deletions

No files matched your search

@@ -2,6 +2,7 @@ package com.example.questionbank.repository;
import com.example.questionbank.domain.ExamAttempt; import com.example.questionbank.domain.ExamAttempt;
import java.util.List; import java.util.List;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -14,6 +15,8 @@ import org.springframework.stereotype.Repository;
*/ */
@Repository @Repository
public interface ExamAttemptRepository extends JpaRepository<ExamAttempt, Long> { public interface ExamAttemptRepository extends JpaRepository<ExamAttempt, Long> {
List<ExamAttempt> findAllByUserLogin(String login);
@Query("select examAttempt from ExamAttempt examAttempt where examAttempt.user.login = ?#{authentication.name}") @Query("select examAttempt from ExamAttempt examAttempt where examAttempt.user.login = ?#{authentication.name}")
List<ExamAttempt> findByUserIsCurrentUser(); List<ExamAttempt> findByUserIsCurrentUser();
@@ -2,6 +2,7 @@ package com.example.questionbank.repository;
import com.example.questionbank.domain.Question; import com.example.questionbank.domain.Question;
import java.util.List; import java.util.List;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -14,6 +15,8 @@ import org.springframework.stereotype.Repository;
*/ */
@Repository @Repository
public interface QuestionRepository extends JpaRepository<Question, Long> { public interface QuestionRepository extends JpaRepository<Question, Long> {
List<Question> findAllBySubjectIdAndActiveTrue(Long subjectId);
default Optional<Question> findOneWithEagerRelationships(Long id) { default Optional<Question> findOneWithEagerRelationships(Long id) {
return this.findOneWithToOneRelationships(id); return this.findOneWithToOneRelationships(id);
} }
@@ -0,0 +1,15 @@
package com.example.questionbank.service.quiz;
import com.example.questionbank.domain.enumeration.OptionKey;
import org.springframework.stereotype.Service;
@Service
public class ExamScoringService {
public ScoringResult score(OptionKey correctOption, OptionKey selectedOption) {
boolean correct = selectedOption != null && selectedOption == correctOption;
return new ScoringResult(correct, correct ? 1 : 0);
}
public record ScoringResult(boolean correct, int awardedPoints) {}
}
@@ -0,0 +1,13 @@
package com.example.questionbank.web.rest.dto;
import com.example.questionbank.domain.enumeration.OptionKey;
public record AnswerResult(
Long questionId,
String questionText,
OptionKey selectedOption,
String selectedContent,
boolean correct,
int awardedPoints,
String explanation
) {}
@@ -0,0 +1,5 @@
package com.example.questionbank.web.rest.dto;
import com.example.questionbank.domain.enumeration.OptionKey;
public record ChoiceView(OptionKey optionKey, String content, Integer displayOrder) {}
@@ -0,0 +1,5 @@
package com.example.questionbank.web.rest.dto;
import java.time.Instant;
public record ExamAttemptStartResult(Long attemptId, Instant startedAt) {}
@@ -0,0 +1,14 @@
package com.example.questionbank.web.rest.dto;
import com.example.questionbank.domain.enumeration.AttemptStatus;
import java.time.Instant;
public record ExamAttemptSummary(
Long attemptId,
Long subjectId,
String subjectName,
Instant startedAt,
Instant submittedAt,
AttemptStatus status,
Integer score
) {}
@@ -0,0 +1,5 @@
package com.example.questionbank.web.rest.dto;
import java.util.List;
public record ExamQuestionView(Long questionId, String prompt, List<ChoiceView> choices) {}
@@ -0,0 +1,6 @@
package com.example.questionbank.web.rest.dto;
import java.time.Instant;
import java.util.List;
public record ExamResult(Long attemptId, int totalScore, Instant submittedAt, List<AnswerResult> answers) {}
@@ -0,0 +1,5 @@
package com.example.questionbank.web.rest.dto;
import jakarta.validation.constraints.NotNull;
public record StartExamRequest(@NotNull Long subjectId) {}
@@ -0,0 +1,6 @@
package com.example.questionbank.web.rest.dto;
import com.example.questionbank.domain.enumeration.OptionKey;
import jakarta.validation.constraints.NotNull;
public record SubmitAnswerRequest(@NotNull Long questionId, OptionKey selectedOption) {}
@@ -0,0 +1,7 @@
package com.example.questionbank.web.rest.dto;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
public record SubmitExamRequest(@NotEmpty List<@Valid SubmitAnswerRequest> answers) {}
@@ -70,7 +70,14 @@ export default defineComponent({
created(): void {}, created(): void {},
methods: { methods: {
save(): void { save(): void {
const message = this.subject.id ? '이 과목의 수정 내용을 저장하시겠습니까?' : '새 과목을 등록하시겠습니까?';
if (!window.confirm(message)) {
return;
}
this.isSaving = true; this.isSaving = true;
if (this.subject.id) { if (this.subject.id) {
this.subjectService() this.subjectService()
.update(this.subject) .update(this.subject)
@@ -13,7 +13,7 @@ export default defineComponent({
const subjectService = inject('subjectService', () => new SubjectService()); const subjectService = inject('subjectService', () => new SubjectService());
const alertService = inject('alertService', () => useAlertService(), true); const alertService = inject('alertService', () => useAlertService(), true);
const itemsPerPage = ref(20); const itemsPerPage = ref(10);
const queryCount: Ref<number> = ref(null); const queryCount: Ref<number> = ref(null);
const page: Ref<number> = ref(1); const page: Ref<number> = ref(1);
const propOrder = ref('id'); const propOrder = ref('id');
@@ -52,7 +52,7 @@
<td> <td>
<router-link :to="{ name: 'SubjectView', params: { subjectId: subject.id } }">{{ subject.id }}</router-link> <router-link :to="{ name: 'SubjectView', params: { subjectId: subject.id } }">{{ subject.id }}</router-link>
</td> </td>
<td>{{ subject.name }}</td> <td>📘{{ subject.name }}</td>
<td>{{ subject.description }}</td> <td>{{ subject.description }}</td>
<td>{{ subject.active }}</td> <td>{{ subject.active }}</td>
<td class="text-end"> <td class="text-end">
@@ -106,7 +106,11 @@
<jhi-item-count :page="page" :total="queryCount" :items-per-page="itemsPerPage"></jhi-item-count> <jhi-item-count :page="page" :total="queryCount" :items-per-page="itemsPerPage"></jhi-item-count>
</div> </div>
<div class="d-flex justify-content-center"> <div class="d-flex justify-content-center">
<b-pagination size="md" :total-rows="totalItems" v-model="page" :per-page="itemsPerPage"></b-pagination> <p>전체 과목 수: {{ totalItems }}개</p>
</div>
<div class="d-flex justify-content-center">
<b-pagination :total-rows="totalItems" v-model="page" :per-page="itemsPerPage"></b-pagination>
</div> </div>
</div> </div>
</div> </div>
@@ -0,0 +1,37 @@
package com.example.questionbank.service.quiz;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.example.questionbank.domain.enumeration.OptionKey;
import org.junit.jupiter.api.Test;
class ExamScoringServiceTest {
private final ExamScoringService service = new ExamScoringService();
@Test
void correctAnswerGetsOnePoint() {
var result = service.score(OptionKey.A, OptionKey.A);
assertTrue(result.correct());
assertEquals(1, result.awardedPoints());
}
@Test
void wrongAnswerGetsZeroPoints() {
var result = service.score(OptionKey.A, OptionKey.D);
assertFalse(result.correct());
assertEquals(0, result.awardedPoints());
}
@Test
void unansweredQuestionGetsZeroPoints() {
var result = service.score(OptionKey.A, null);
assertFalse(result.correct());
assertEquals(0, result.awardedPoints());
}
}