From 5c35bc788bb102d4b320abc42e2efe1f082e241a Mon Sep 17 00:00:00 2001 From: Minsu Kim Date: Fri, 10 Jan 2025 13:20:08 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8B=9C=ED=97=98=EC=9D=98=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91,=20=EC=A2=85=EB=A3=8C=20=EC=8B=9C=EA=B0=84=EC=9D=84?= =?UTF-8?q?=20=EC=A7=80=EC=A0=95=ED=95=9C=20=EC=B6=9C=EC=A0=9C=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EC=9D=84=20=EC=A0=9C=EA=B1=B0=ED=95=9C=EB=8B=A4.=20(#?= =?UTF-8?q?20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 사용하지 않는 vercel analytics, speed-insights 제거 * docs: 프로젝트 개요 주소 링크로 변경 * refactor: 시험 제출 시startAt, endAt 필드 제거 * refactor: 제출된 시험 문제 중 지문이 있는 경우에만 보여준다. * refactor: 시험 제출 요청 시 startAt, endAt 필드를 지운다. * refactor: exam 테이블에 startAt, endAt 컬럼을 지운다. * refactor: startAt, endAt 필드를 지운 후 테스트를 성공시킨다. --- README.md | 8 +- .../main/java/com/fluffy/DataInitializer.java | 13 ++-- .../api/request/PublishExamWebRequest.java | 9 +-- .../fluffy/exam/application/ExamService.java | 2 +- .../request/PublishExamAppRequest.java | 3 - .../java/com/fluffy/exam/domain/Exam.java | 7 +- .../com/fluffy/exam/domain/ExamPeriod.java | 64 ---------------- .../main/resources/db/migration/V1__init.sql | 20 ++--- .../migration/V4__drop_columns_from_exam.sql | 3 + .../com/fluffy/exam/api/ExamDocumentTest.java | 8 +- .../application/ExamQueryServiceTest.java | 20 ++--- .../fluffy/exam/domain/ExamPeriodTest.java | 73 ------------------- .../exam/domain/ExamRepositoryTest.java | 12 +-- .../java/com/fluffy/exam/domain/ExamTest.java | 21 +++--- .../SubmissionQueryServiceTest.java | 8 +- .../application/SubmissionServiceTest.java | 2 +- .../domain/SubmissionRepositoryTest.java | 4 +- web/package.json | 3 - web/pnpm-lock.yaml | 66 ----------------- web/src/api/examAPI.ts | 2 - .../components/overview/ExamPublishButton.tsx | 73 ++----------------- .../questions/view/QuestionViewTemplate.tsx | 4 +- web/src/main.tsx | 9 +-- 23 files changed, 72 insertions(+), 362 deletions(-) delete mode 100644 server/src/main/java/com/fluffy/exam/domain/ExamPeriod.java create mode 100644 server/src/main/resources/db/migration/V4__drop_columns_from_exam.sql delete mode 100644 server/src/test/java/com/fluffy/exam/domain/ExamPeriodTest.java diff --git a/README.md b/README.md index 1926b49..4736537 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ ## 프로젝트 개요 -- 서버 주소: https://api.fluppy.run -- API 명세서: https://api.fluffy.run/docs/index.html -- 웹 프론트엔드 주소: https://fluppy.run -- 서비스 개발 블로그: https://alstn113.tistory.com/tag/플러피 +- [서버 주소](https://api.fluffy.run) +- [API 명세서](https://api.fluffy.run/docs/index.html) +- [웹 프론트엔드 주소](https://fluffy.run) +- [서비스 개발 블로그](https://alstn113.tistory.com/tag/플러피) ## 서버 diff --git a/server/src/main/java/com/fluffy/DataInitializer.java b/server/src/main/java/com/fluffy/DataInitializer.java index 4b60042..c572ea3 100644 --- a/server/src/main/java/com/fluffy/DataInitializer.java +++ b/server/src/main/java/com/fluffy/DataInitializer.java @@ -7,7 +7,6 @@ import com.fluffy.exam.domain.ExamRepository; import com.fluffy.exam.domain.Question; import com.fluffy.exam.domain.QuestionOption; -import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.boot.ApplicationArguments; @@ -89,7 +88,7 @@ private void init() { )), Question.trueOrFalse("펭귄은 날 수 있는 새이다.", "", false) )); - exam1.publish(null, null); + exam1.publish(); examRepository.save(exam1); Exam exam2 = Exam.create("역사 시험", member1.getId()); @@ -128,7 +127,7 @@ private void init() { Question.shortAnswer("로제타 스톤의 중요성은 무엇인가요?", "", "고대 이집트 문자의 해독"), Question.longAnswer("콜럼버스의 발견과 그 영향에 대해 설명하세요.", "") )); - exam2.publish(null, LocalDateTime.now().plusDays(3)); + exam2.publish(); examRepository.save(exam2); Exam exam3 = Exam.create("영어 시험", member2.getId()); @@ -172,7 +171,7 @@ private void init() { Question.shortAnswer("‘beneficial’의 의미는 무엇인가요?", "", "유익한"), Question.longAnswer("‘I wish I had studied harder’ 문장의 의미를 설명하세요.", "") )); - exam3.publish(LocalDateTime.now().plusDays(1), LocalDateTime.now().plusDays(2)); + exam3.publish(); examRepository.save(exam3); Exam exam4 = Exam.create("과학 시험", member2.getId()); @@ -209,7 +208,7 @@ private void init() { Question.trueOrFalse("빛은 물질을 통과할 수 없다.", "", false), Question.longAnswer("지구의 내부 구조에 대해 설명하세요.", "") )); - exam4.publish(LocalDateTime.now().plusDays(2), LocalDateTime.now().plusDays(4)); + exam4.publish(); examRepository.save(exam4); Exam exam5 = Exam.create("문화 시험", member2.getId()); @@ -248,7 +247,7 @@ private void init() { Question.shortAnswer("‘매트릭스’ 영화의 주제는 무엇인가요?", "", "가상 현실과 인간의 자유 의지"), Question.longAnswer("세계 각국의 전통 축제에 대해 설명하세요.", "") )); - exam5.publish(LocalDateTime.now().plusDays(1), LocalDateTime.now().plusDays(3)); + exam5.publish(); examRepository.save(exam5); Exam exam6 = Exam.create("컴퓨터 시험", member3.getId()); @@ -287,7 +286,7 @@ private void init() { Question.shortAnswer("오픈 소스 소프트웨어의 장점은 무엇인가요?", "", "자유로운 사용, 수정 가능"), Question.longAnswer("기술 발전이 사회에 미친 영향에 대해 설명하세요.", "") )); - exam6.publish(LocalDateTime.now().plusDays(2), LocalDateTime.now().plusDays(4)); + exam6.publish(); examRepository.save(exam6); } } diff --git a/server/src/main/java/com/fluffy/exam/api/request/PublishExamWebRequest.java b/server/src/main/java/com/fluffy/exam/api/request/PublishExamWebRequest.java index 23bfe24..189889e 100644 --- a/server/src/main/java/com/fluffy/exam/api/request/PublishExamWebRequest.java +++ b/server/src/main/java/com/fluffy/exam/api/request/PublishExamWebRequest.java @@ -3,15 +3,10 @@ import com.fluffy.exam.application.request.PublishExamAppRequest; import com.fluffy.exam.application.request.question.QuestionAppRequest; import com.fluffy.global.web.Accessor; -import java.time.LocalDateTime; import java.util.List; -public record PublishExamWebRequest( - List questions, - LocalDateTime startAt, - LocalDateTime endAt -) { +public record PublishExamWebRequest(List questions) { public PublishExamAppRequest toAppRequest(Long examId, Accessor accessor) { - return new PublishExamAppRequest(examId, questions, startAt, endAt, accessor); + return new PublishExamAppRequest(examId, questions, accessor); } } diff --git a/server/src/main/java/com/fluffy/exam/application/ExamService.java b/server/src/main/java/com/fluffy/exam/application/ExamService.java index baf0ae7..5a06174 100644 --- a/server/src/main/java/com/fluffy/exam/application/ExamService.java +++ b/server/src/main/java/com/fluffy/exam/application/ExamService.java @@ -53,7 +53,7 @@ public void publish(PublishExamAppRequest request) { List questions = questionMapper.toQuestions(request.questions()); exam.updateQuestions(questions); - exam.publish(request.startAt(), request.endAt()); + exam.publish(); } private Exam validateExamAuthor(Long examId, Accessor accessor) { diff --git a/server/src/main/java/com/fluffy/exam/application/request/PublishExamAppRequest.java b/server/src/main/java/com/fluffy/exam/application/request/PublishExamAppRequest.java index 347de3b..bba2b83 100644 --- a/server/src/main/java/com/fluffy/exam/application/request/PublishExamAppRequest.java +++ b/server/src/main/java/com/fluffy/exam/application/request/PublishExamAppRequest.java @@ -2,14 +2,11 @@ import com.fluffy.exam.application.request.question.QuestionAppRequest; import com.fluffy.global.web.Accessor; -import java.time.LocalDateTime; import java.util.List; public record PublishExamAppRequest( Long examId, List questions, - LocalDateTime startAt, - LocalDateTime endAt, Accessor accessor ) { } diff --git a/server/src/main/java/com/fluffy/exam/domain/Exam.java b/server/src/main/java/com/fluffy/exam/domain/Exam.java index 3009ffc..f9dee88 100644 --- a/server/src/main/java/com/fluffy/exam/domain/Exam.java +++ b/server/src/main/java/com/fluffy/exam/domain/Exam.java @@ -10,7 +10,6 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import java.time.LocalDateTime; import java.util.List; import lombok.AccessLevel; import lombok.Getter; @@ -41,9 +40,6 @@ public class Exam extends AuditableEntity { @Embedded private final QuestionGroup questionGroup = new QuestionGroup(); - @Embedded - private ExamPeriod examPeriod; - @Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT FALSE") private boolean isSingleAttempt = false; @@ -57,7 +53,7 @@ public static Exam create(String title, Long memberId) { return exam; } - public void publish(LocalDateTime startAt, LocalDateTime endAt) { + public void publish() { if (status.isPublished()) { throw new BadRequestException("시험은 이미 출시되었습니다."); } @@ -66,7 +62,6 @@ public void publish(LocalDateTime startAt, LocalDateTime endAt) { throw new BadRequestException("시험을 출시하기 위해서는 최소 1개 이상의 문제를 추가해야 합니다."); } - this.examPeriod = ExamPeriod.create(startAt, endAt); this.status = ExamStatus.PUBLISHED; } diff --git a/server/src/main/java/com/fluffy/exam/domain/ExamPeriod.java b/server/src/main/java/com/fluffy/exam/domain/ExamPeriod.java deleted file mode 100644 index f5ddf99..0000000 --- a/server/src/main/java/com/fluffy/exam/domain/ExamPeriod.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.fluffy.exam.domain; - -import com.fluffy.global.exception.BadRequestException; -import jakarta.annotation.Nullable; -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Embeddable -@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) -@Getter -public class ExamPeriod { - - @Column - private LocalDateTime startAt; - - @Column - private LocalDateTime endAt; - - public static ExamPeriod create(@Nullable LocalDateTime startAt, @Nullable LocalDateTime endAt) { - LocalDateTime validatedStartAt = validateAndGetStartAt(startAt); - LocalDateTime validatedEndAt = validateAndGetEndAt(validatedStartAt, endAt); - - return new ExamPeriod(validatedStartAt, validatedEndAt); - } - - private static LocalDateTime validateAndGetStartAt(@Nullable LocalDateTime startAt) { - LocalDateTime nowTruncated = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES); - - if (startAt == null) { - return nowTruncated; - } - - // 시작 시간은 현재 시간과 같거나 이후여야 함. - LocalDateTime startAtTruncated = startAt.truncatedTo(ChronoUnit.MINUTES); - if (nowTruncated.isAfter(startAtTruncated)) { - throw new BadRequestException("시작 시간은 현재 시간과 같거나 이후여야 합니다."); - } - - return startAtTruncated; - } - - private static LocalDateTime validateAndGetEndAt(LocalDateTime startAtTruncated, @Nullable LocalDateTime endAt) { - if (endAt == null) { - return null; - } - - // 시작 시간은 종료 시간과 같을 수 없고, 이전이어야 함. - LocalDateTime endAtTruncated = endAt.truncatedTo(ChronoUnit.MINUTES); - if (startAtTruncated.isAfter(endAtTruncated)) { - throw new BadRequestException("시작 시간은 종료 시간 이전이어야 합니다."); - } - - return endAtTruncated; - } - - private ExamPeriod(LocalDateTime startAt, LocalDateTime endAt) { - this.startAt = startAt; - this.endAt = endAt; - } -} diff --git a/server/src/main/resources/db/migration/V1__init.sql b/server/src/main/resources/db/migration/V1__init.sql index 2e24869..1a1a4ab 100644 --- a/server/src/main/resources/db/migration/V1__init.sql +++ b/server/src/main/resources/db/migration/V1__init.sql @@ -1,13 +1,13 @@ CREATE TABLE IF NOT EXISTS member ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY, - name VARCHAR(255) NOT NULL, - email VARCHAR(255), - avatar_url VARCHAR(255) NOT NULL, - social_id VARCHAR(255) NOT NULL, - provider VARCHAR(20) NOT NULL, - created_at TIMESTAMP(6) NOT NULL, - updated_at TIMESTAMP(6) NOT NULL, + id BIGINT GENERATED BY DEFAULT AS IDENTITY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255), + avatar_url VARCHAR(255) NOT NULL, + social_id VARCHAR(255) NOT NULL, + provider VARCHAR(20) NOT NULL, + created_at TIMESTAMP(6) NOT NULL, + updated_at TIMESTAMP(6) NOT NULL, PRIMARY KEY (id) ); @@ -16,7 +16,7 @@ CREATE TABLE IF NOT EXISTS exam id BIGINT GENERATED BY DEFAULT AS IDENTITY, member_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, - description TEXT NOT NULL, + description TEXT NOT NULL, status VARCHAR(20) NOT NULL, created_at TIMESTAMP(6) NOT NULL, start_at TIMESTAMP(6), @@ -45,7 +45,7 @@ CREATE TABLE IF NOT EXISTS question id BIGINT GENERATED BY DEFAULT AS IDENTITY, exam_id BIGINT, text VARCHAR(255) NOT NULL, - correct_answer VARCHAR(255), + correct_answer VARCHAR(255), type VARCHAR(50) NOT NULL, PRIMARY KEY (id) ); diff --git a/server/src/main/resources/db/migration/V4__drop_columns_from_exam.sql b/server/src/main/resources/db/migration/V4__drop_columns_from_exam.sql new file mode 100644 index 0000000..b564271 --- /dev/null +++ b/server/src/main/resources/db/migration/V4__drop_columns_from_exam.sql @@ -0,0 +1,3 @@ +ALTER TABLE exam + DROP COLUMN start_at, + DROP COLUMN end_at; diff --git a/server/src/test/java/com/fluffy/exam/api/ExamDocumentTest.java b/server/src/test/java/com/fluffy/exam/api/ExamDocumentTest.java index 5ef2134..2340ae6 100644 --- a/server/src/test/java/com/fluffy/exam/api/ExamDocumentTest.java +++ b/server/src/test/java/com/fluffy/exam/api/ExamDocumentTest.java @@ -351,9 +351,7 @@ void publish() throws Exception { List.of(new QuestionOptionRequest("선택1", true), new QuestionOptionRequest("선택2", true))), new TrueOrFalseQuestionAppRequest("질문5", "지문", "TRUE_OR_FALSE", true) - ), - LocalDateTime.now(), - LocalDateTime.now().plusDays(1) + ) ); doNothing().when(examService).publish(any()); @@ -381,9 +379,7 @@ void publish() throws Exception { fieldWithPath("questions[].trueOrFalse").description("true or false 정답").optional(), fieldWithPath("questions[].options").description("선택지 목록").optional(), fieldWithPath("questions[].options[].text").description("선택지 내용").optional(), - fieldWithPath("questions[].options[].isCorrect").description("정답 여부").optional(), - fieldWithPath("startAt").description("시작 시간").optional(), - fieldWithPath("endAt").description("종료 시간").optional() + fieldWithPath("questions[].options[].isCorrect").description("정답 여부").optional() ) )); } diff --git a/server/src/test/java/com/fluffy/exam/application/ExamQueryServiceTest.java b/server/src/test/java/com/fluffy/exam/application/ExamQueryServiceTest.java index 2ae1e64..f292d44 100644 --- a/server/src/test/java/com/fluffy/exam/application/ExamQueryServiceTest.java +++ b/server/src/test/java/com/fluffy/exam/application/ExamQueryServiceTest.java @@ -48,17 +48,17 @@ void getPublishedExamSummaries() { Exam exam1 = Exam.create("시험 제목1", author.getId()); exam1.updateQuestions(List.of(Question.shortAnswer("질문1", "지문", "답1"))); - exam1.publish(null, null); + exam1.publish(); examRepository.save(exam1); Exam exam2 = Exam.create("시험 제목2", author.getId()); exam2.updateQuestions(List.of(Question.shortAnswer("질문2", "지문", "답2"))); - exam2.publish(null, null); + exam2.publish(); examRepository.save(exam2); Exam exam3 = Exam.create("시험 제목3", author.getId()); exam3.updateQuestions(List.of(Question.shortAnswer("질문3", "지문", "답3"))); - exam3.publish(null, null); + exam3.publish(); examRepository.save(exam3); // when @@ -86,12 +86,12 @@ void getMyExamSummaries() { Exam exam1 = Exam.create("시험 제목1", author.getId()); exam1.updateQuestions(List.of(Question.shortAnswer("질문1", "지문", "답1"))); - exam1.publish(null, null); + exam1.publish(); examRepository.save(exam1); Exam exam2 = Exam.create("시험 제목2", author.getId()); exam2.updateQuestions(List.of(Question.shortAnswer("질문2", "지문", "답2"))); - exam2.publish(null, null); + exam2.publish(); examRepository.save(exam2); Exam otherExam1 = Exam.create("시험 제목3", author.getId()); @@ -100,12 +100,12 @@ void getMyExamSummaries() { Exam exam4 = Exam.create("시험 제목4", another.getId()); exam4.updateQuestions(List.of(Question.shortAnswer("질문4", "지문", "답4"))); - exam4.publish(null, null); + exam4.publish(); examRepository.save(exam4); Exam exam5 = Exam.create("시험 제목5", author.getId()); exam5.updateQuestions(List.of(Question.shortAnswer("질문5", "지문", "답5"))); - exam5.publish(null, null); + exam5.publish(); examRepository.save(exam5); // when @@ -131,7 +131,7 @@ void getExamDetail() { Exam exam = Exam.create("시험 제목", member.getId()); exam.updateQuestions(List.of(Question.shortAnswer("질문", "지문", "답"))); - exam.publish(null, null); + exam.publish(); examRepository.save(exam); // when @@ -157,7 +157,7 @@ void getExamWithAnswers() { Exam exam = Exam.create("시험 제목", member.getId()); exam.updateQuestions(List.of(Question.shortAnswer("질문", "지문", "답"))); - exam.publish(null, null); + exam.publish(); examRepository.save(exam); // when @@ -187,7 +187,7 @@ void getExamWithAnswersFailWhenNotWrittenBy() { Exam exam = Exam.create("시험 제목", member.getId()); exam.updateQuestions(List.of(Question.shortAnswer("질문", "지문", "답"))); - exam.publish(null, null); + exam.publish(); examRepository.save(exam); // when diff --git a/server/src/test/java/com/fluffy/exam/domain/ExamPeriodTest.java b/server/src/test/java/com/fluffy/exam/domain/ExamPeriodTest.java deleted file mode 100644 index 9d3be26..0000000 --- a/server/src/test/java/com/fluffy/exam/domain/ExamPeriodTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.fluffy.exam.domain; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - -import com.fluffy.global.exception.BadRequestException; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class ExamPeriodTest { - - @Test - @DisplayName("시작 시간과 종료 시간은 분 단위로 설정된다.") - void startAtAndEndAtShouldBeTruncatedToMinutes() { - // given - LocalDateTime startAt = LocalDateTime.now(); - LocalDateTime endAt = startAt.plusMinutes(1); - - // when - ExamPeriod examPeriod = ExamPeriod.create(startAt, endAt); - - // then - assertAll( - () -> assertThat(examPeriod.getStartAt()).isEqualTo(startAt.truncatedTo(ChronoUnit.MINUTES)), - () -> assertThat(examPeriod.getEndAt()).isEqualTo(endAt.truncatedTo(ChronoUnit.MINUTES)) - ); - } - - @Test - @DisplayName("시작 시간이 null이고, 종료 시간이 null인 경우, 시작 시간은 현재 시간으로 설정된다.") - void setStartAtToNowWhenStartAtIsNullAndEndAtIsNull() { - // given - LocalDateTime now = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES); - - // when - ExamPeriod examPeriod = ExamPeriod.create(null, null); - - // then - assertAll( - () -> assertThat(examPeriod.getStartAt()).isEqualTo(now), - () -> assertThat(examPeriod.getEndAt()).isNull() - ); - } - - @Test - @DisplayName("시작 시간은 현재 시간보다 같거나 이후여야 한다.") - void startAtShouldBeSameOrAfterNow() { - // given - LocalDateTime now = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES); - LocalDateTime startAt = now.minusMinutes(1); - - // when & then - assertThatThrownBy(() -> ExamPeriod.create(startAt, null)) - .isInstanceOf(BadRequestException.class) - .hasMessage("시작 시간은 현재 시간과 같거나 이후여야 합니다."); - } - - @Test - @DisplayName("시작 시간은 종료 시간보다 이전이어야 한다.") - void startAtShouldBeBeforeEndAt() { - // given - LocalDateTime startAt = LocalDateTime.now(); - LocalDateTime endAt = startAt.minusMinutes(1); - - // when & then - assertThatThrownBy(() -> ExamPeriod.create(startAt, endAt)) - .isInstanceOf(BadRequestException.class) - .hasMessage("시작 시간은 종료 시간 이전이어야 합니다."); - } -} diff --git a/server/src/test/java/com/fluffy/exam/domain/ExamRepositoryTest.java b/server/src/test/java/com/fluffy/exam/domain/ExamRepositoryTest.java index 62e89ba..b413aef 100644 --- a/server/src/test/java/com/fluffy/exam/domain/ExamRepositoryTest.java +++ b/server/src/test/java/com/fluffy/exam/domain/ExamRepositoryTest.java @@ -35,7 +35,7 @@ void findPublishedExamSummaries() { Exam publishedExam1 = Exam.create("publishedExam1", member1.getId()); publishedExam1.updateQuestions(List.of(Question.shortAnswer("질문1", "지문", "답1"))); - publishedExam1.publish(null, null); + publishedExam1.publish(); examRepository.save(publishedExam1); Exam publishedExam2 = Exam.create("publishedExam2", member1.getId()); @@ -43,7 +43,7 @@ void findPublishedExamSummaries() { Question.shortAnswer("질문3", "지문", "답3"), Question.shortAnswer("질문5", "지문", "답5") )); - publishedExam2.publish(null, null); + publishedExam2.publish(); examRepository.save(publishedExam2); Exam draftExam1 = Exam.create("draftExam1", member1.getId()); @@ -56,7 +56,7 @@ void findPublishedExamSummaries() { Question.shortAnswer("질문6", "지문", "답6"), Question.shortAnswer("질문7", "지문", "답7") )); - publishedExam3.publish(null, null); + publishedExam3.publish(); examRepository.save(publishedExam3); // when @@ -88,7 +88,7 @@ void findMyExamSummaries() { Exam publishedExam1 = Exam.create("publishedExam1", member1.getId()); publishedExam1.updateQuestions(List.of(Question.shortAnswer("질문1", "지문", "답1"))); - publishedExam1.publish(null, null); + publishedExam1.publish(); examRepository.save(publishedExam1); Exam publishedExam2 = Exam.create("publishedExam2", member2.getId()); @@ -96,7 +96,7 @@ void findMyExamSummaries() { Question.shortAnswer("질문3", "지문", "답3"), Question.shortAnswer("질문5", "지문", "답5") )); - publishedExam2.publish(null, null); + publishedExam2.publish(); examRepository.save(publishedExam2); Exam draftExam1 = Exam.create("draftExam1", member1.getId()); @@ -109,7 +109,7 @@ void findMyExamSummaries() { Question.shortAnswer("질문6", "지문", "답6"), Question.shortAnswer("질문7", "지문", "답7") )); - publishedExam3.publish(null, null); + publishedExam3.publish(); examRepository.save(publishedExam3); // when diff --git a/server/src/test/java/com/fluffy/exam/domain/ExamTest.java b/server/src/test/java/com/fluffy/exam/domain/ExamTest.java index 64d0224..533a826 100644 --- a/server/src/test/java/com/fluffy/exam/domain/ExamTest.java +++ b/server/src/test/java/com/fluffy/exam/domain/ExamTest.java @@ -72,13 +72,10 @@ void publish() { )); // when - exam.publish(null, null); + exam.publish(); // then - assertAll( - () -> assertThat(exam.getStatus()).isEqualTo(ExamStatus.PUBLISHED), - () -> assertThat(exam.getExamPeriod().getStartAt()).isNotNull() - ); + assertThat(exam.getStatus()).isEqualTo(ExamStatus.PUBLISHED); } @Test @@ -90,10 +87,10 @@ void publishTwice() { Question.shortAnswer("단답형1", "지문", "답1"), Question.trueOrFalse("O/X1", "지문", true) )); - exam.publish(null, null); + exam.publish(); // when & then - assertThatThrownBy(() -> exam.publish(null, null)) + assertThatThrownBy(() -> exam.publish()) .isInstanceOf(BadRequestException.class) .hasMessage("시험은 이미 출시되었습니다."); } @@ -105,7 +102,7 @@ void publishWithoutQuestions() { Exam exam = Exam.create("시험 제목", 1L); // when & then - assertThatThrownBy(() -> exam.publish(null, null)) + assertThatThrownBy(() -> exam.publish()) .isInstanceOf(BadRequestException.class) .hasMessage("시험을 출시하기 위해서는 최소 1개 이상의 문제를 추가해야 합니다."); } @@ -119,7 +116,7 @@ void updateQuestionsAfterPublish() { Question.shortAnswer("단답형1", "지문", "답1"), Question.trueOrFalse("O/X1", "지문", true) )); - exam.publish(null, null); + exam.publish(); // when & then List questions = List.of(Question.shortAnswer("단답형2", "지문", "답2")); @@ -150,7 +147,7 @@ void updateTitleAfterPublish() { Question.shortAnswer("단답형1", "지문", "답1"), Question.trueOrFalse("O/X1", "지문", true) )); - exam.publish(null, null); + exam.publish(); // when & then assertThatThrownBy(() -> exam.updateTitle("수정된 시험 제목")) @@ -180,7 +177,7 @@ void updateDescriptionAfterPublish() { Question.shortAnswer("단답형1", "지문", "답1"), Question.trueOrFalse("O/X1", "지문", true) )); - exam.publish(null, null); + exam.publish(); // when & then assertThatThrownBy(() -> exam.updateDescription("시험 설명")) @@ -210,7 +207,7 @@ void updateIsSingleAttemptAfterPublish() { Question.shortAnswer("단답형1", "지문", "답1"), Question.trueOrFalse("O/X1", "지문", true) )); - exam.publish(null, null); + exam.publish(); // when & then assertThatThrownBy(() -> exam.updateIsSingleAttempt(true)) diff --git a/server/src/test/java/com/fluffy/submission/application/SubmissionQueryServiceTest.java b/server/src/test/java/com/fluffy/submission/application/SubmissionQueryServiceTest.java index 0256b1e..b22b311 100644 --- a/server/src/test/java/com/fluffy/submission/application/SubmissionQueryServiceTest.java +++ b/server/src/test/java/com/fluffy/submission/application/SubmissionQueryServiceTest.java @@ -47,7 +47,7 @@ void getSummariesByExamId() { Exam exam = Exam.create("title", author.getId()); exam.updateQuestions(List.of(Question.shortAnswer("질문", "지문", "답"))); - exam.publish(null, null); + exam.publish(); examRepository.save(exam); Member submitter1 = MemberTestData.defaultMember().build(); @@ -91,7 +91,7 @@ void getSummariesByExamIdFailWhenNotAuthor() { Exam exam = Exam.create("title", author.getId()); exam.updateQuestions(List.of(Question.shortAnswer("질문", "지문", "답"))); - exam.publish(null, null); + exam.publish(); examRepository.save(exam); // when @@ -112,7 +112,7 @@ void getDetail() { Exam exam = Exam.create("title", author.getId()); exam.updateQuestions(List.of(Question.shortAnswer("질문", "지문", "질문 답"))); - exam.publish(null, null); + exam.publish(); examRepository.save(exam); Member submitter = MemberTestData.defaultMember().build(); @@ -151,7 +151,7 @@ void getDetailFailWhenNotAuthor() { Exam exam = Exam.create("title", author.getId()); exam.updateQuestions(List.of(Question.shortAnswer("질문", "지문", "질문 답"))); - exam.publish(null, null); + exam.publish(); examRepository.save(exam); Member submitter = MemberTestData.defaultMember().build(); diff --git a/server/src/test/java/com/fluffy/submission/application/SubmissionServiceTest.java b/server/src/test/java/com/fluffy/submission/application/SubmissionServiceTest.java index 2601f8d..68d1c39 100644 --- a/server/src/test/java/com/fluffy/submission/application/SubmissionServiceTest.java +++ b/server/src/test/java/com/fluffy/submission/application/SubmissionServiceTest.java @@ -46,7 +46,7 @@ void submit() throws InterruptedException { Exam exam = Exam.create("시험 제목", member1.getId()); exam.updateQuestions(List.of(Question.shortAnswer("단답형1", "지문", "답1"))); exam.updateIsSingleAttempt(true); - exam.publish(null, null); + exam.publish(); examRepository.save(exam); // when diff --git a/server/src/test/java/com/fluffy/submission/domain/SubmissionRepositoryTest.java b/server/src/test/java/com/fluffy/submission/domain/SubmissionRepositoryTest.java index abe80d9..0d8d623 100644 --- a/server/src/test/java/com/fluffy/submission/domain/SubmissionRepositoryTest.java +++ b/server/src/test/java/com/fluffy/submission/domain/SubmissionRepositoryTest.java @@ -37,12 +37,12 @@ void findSubmissionSummariesByExamId() { Exam exam = Exam.create("exam", author.getId()); exam.updateQuestions(List.of(Question.shortAnswer("질문1", "지문", "답1"))); - exam.publish(null, null); + exam.publish(); examRepository.save(exam); Exam otherExam = Exam.create("otherExam", author.getId()); otherExam.updateQuestions(List.of(Question.shortAnswer("질문2", "지문", "답2"))); - otherExam.publish(null, null); + otherExam.publish(); examRepository.save(otherExam); Member member1 = MemberTestData.defaultMember().build(); diff --git a/web/package.json b/web/package.json index 595daab..97b472b 100644 --- a/web/package.json +++ b/web/package.json @@ -13,11 +13,8 @@ "dependencies": { "@daveyplate/nextui-fixed-avatar": "^1.0.4", "@hello-pangea/dnd": "^17.0.0", - "@internationalized/date": "^3.6.0", "@nextui-org/react": "^2.4.8", "@tanstack/react-query": "^5.59.16", - "@vercel/analytics": "^1.4.1", - "@vercel/speed-insights": "^1.1.0", "axios": "^1.7.7", "date-fns": "^4.1.0", "framer-motion": "^11.13.1", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index d10bc39..9e6d379 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -14,21 +14,12 @@ importers: '@hello-pangea/dnd': specifier: ^17.0.0 version: 17.0.0(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@internationalized/date': - specifier: ^3.6.0 - version: 3.6.0 '@nextui-org/react': specifier: ^2.4.8 version: 2.4.8(@types/react@18.3.12)(framer-motion@11.13.1(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.15) '@tanstack/react-query': specifier: ^5.59.16 version: 5.59.19(react@18.3.1) - '@vercel/analytics': - specifier: ^1.4.1 - version: 1.4.1(react@18.3.1) - '@vercel/speed-insights': - specifier: ^1.1.0 - version: 1.1.0(react@18.3.1) axios: specifier: ^1.7.7 version: 1.7.7 @@ -1736,55 +1727,6 @@ packages: resolution: {integrity: sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vercel/analytics@1.4.1': - resolution: {integrity: sha512-ekpL4ReX2TH3LnrRZTUKjHHNpNy9S1I7QmS+g/RQXoSUQ8ienzosuX7T9djZ/s8zPhBx1mpHP/Rw5875N+zQIQ==} - peerDependencies: - '@remix-run/react': ^2 - '@sveltejs/kit': ^1 || ^2 - next: '>= 13' - react: ^18 || ^19 || ^19.0.0-rc - svelte: '>= 4' - vue: ^3 - vue-router: ^4 - peerDependenciesMeta: - '@remix-run/react': - optional: true - '@sveltejs/kit': - optional: true - next: - optional: true - react: - optional: true - svelte: - optional: true - vue: - optional: true - vue-router: - optional: true - - '@vercel/speed-insights@1.1.0': - resolution: {integrity: sha512-rAXxuhhO4mlRGC9noa5F7HLMtGg8YF1zAN6Pjd1Ny4pII4cerhtwSG4vympbCl+pWkH7nBS9kVXRD4FAn54dlg==} - peerDependencies: - '@sveltejs/kit': ^1 || ^2 - next: '>= 13' - react: ^18 || ^19 || ^19.0.0-rc - svelte: '>= 4' - vue: ^3 - vue-router: ^4 - peerDependenciesMeta: - '@sveltejs/kit': - optional: true - next: - optional: true - react: - optional: true - svelte: - optional: true - vue: - optional: true - vue-router: - optional: true - '@vitejs/plugin-react@4.3.3': resolution: {integrity: sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -5413,14 +5355,6 @@ snapshots: '@typescript-eslint/types': 8.13.0 eslint-visitor-keys: 3.4.3 - '@vercel/analytics@1.4.1(react@18.3.1)': - optionalDependencies: - react: 18.3.1 - - '@vercel/speed-insights@1.1.0(react@18.3.1)': - optionalDependencies: - react: 18.3.1 - '@vitejs/plugin-react@4.3.3(vite@5.4.10(@types/node@22.10.1))': dependencies: '@babel/core': 7.26.0 diff --git a/web/src/api/examAPI.ts b/web/src/api/examAPI.ts index 1306673..603c163 100644 --- a/web/src/api/examAPI.ts +++ b/web/src/api/examAPI.ts @@ -135,8 +135,6 @@ interface PublishExamParams { interface PublishExamRequest { questions: QuestionBaseRequest[]; - startAt: string | null; - endAt: string | null; } interface UpdateExamQuestionsParams { diff --git a/web/src/components/overview/ExamPublishButton.tsx b/web/src/components/overview/ExamPublishButton.tsx index 354d33e..36c47af 100644 --- a/web/src/components/overview/ExamPublishButton.tsx +++ b/web/src/components/overview/ExamPublishButton.tsx @@ -3,18 +3,14 @@ import usePublishExam from '@/hooks/api/exam/usePublishExam'; import useExamEditorStore from '@/stores/useExamEditorStore'; import { Button, - DatePicker, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, - Switch, useDisclosure, } from '@nextui-org/react'; -import { useState } from 'react'; import { useNavigate } from 'react-router'; -import { now, getLocalTimeZone, ZonedDateTime } from '@internationalized/date'; interface ExamPublishButtonProps { examId: number; @@ -26,39 +22,12 @@ const ExamPublishButton = ({ examId }: ExamPublishButtonProps) => { const { mutate: publishExamMutate } = usePublishExam(); const navigate = useNavigate(); - const [startAt, setStartAt] = useState(now(getLocalTimeZone()).add({ hours: 1 })); - const [endAt, setEndAt] = useState(now(getLocalTimeZone()).add({ hours: 3 })); - const [isStartAtNow, setIsStartAtNow] = useState(false); - const [isEndAtInfinite, setIsEndAtInfinite] = useState(false); - - const parseDate = (date: ZonedDateTime) => { - return date.toString().substring(0, 16); - }; - - const validatePeriod = () => { - if (!isStartAtNow && !startAt) return false; - if (!isEndAtInfinite && !endAt) return false; - - const parsedStartAt = isStartAtNow ? null : parseDate(startAt); - const parsedEndAt = isEndAtInfinite ? null : parseDate(endAt); - - if (parsedStartAt && parsedEndAt && parsedStartAt >= parsedEndAt) return false; - if (parsedStartAt && parsedStartAt < now(getLocalTimeZone()).toString().substring(0, 16)) - return false; - - return true; - }; - const handlePublishExam = () => { - if (!validatePeriod()) return; - publishExamMutate( { examId, request: { questions, - startAt: isStartAtNow ? null : parseDate(startAt), - endAt: isEndAtInfinite ? null : parseDate(endAt), }, }, { @@ -75,52 +44,22 @@ const ExamPublishButton = ({ examId }: ExamPublishButtonProps) => { - + {(onClose) => ( <> - - 시험 출제할 날짜와 시간을 선택하세요 - + 시험을 출제하시겠습니까? -
-
-

시험 시작일을 설정합니다.

- - - 시험 시작일을 현재 시간으로 설정 - -
-
-

시험 종료일을 설정합니다.

- - - 시험 종료일을 무기한으로 설정 - -
+
+

시험 출제 후에는 수정이 불가능합니다.

+

출제된 시험은 대시보드에서 확인할 수 있습니다.

- diff --git a/web/src/components/questions/view/QuestionViewTemplate.tsx b/web/src/components/questions/view/QuestionViewTemplate.tsx index 6d7d77e..7bfbd1d 100644 --- a/web/src/components/questions/view/QuestionViewTemplate.tsx +++ b/web/src/components/questions/view/QuestionViewTemplate.tsx @@ -34,7 +34,9 @@ const QuestionViewTemplate = () => { label: 'text-xl font-semibold text-gray-800', }} /> -