diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java index 8dad7ae..b22ac5b 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/childProblem/QChildProblem.java @@ -26,6 +26,8 @@ public class QChildProblem extends EntityPathBase { public final com.moplus.moplus_server.domain.problem.domain.QAnswer answer; + public final EnumPath answerType = createEnum("answerType", com.moplus.moplus_server.domain.problem.domain.problem.AnswerType.class); + public final SetPath> conceptTagIds = this.>createSet("conceptTagIds", Long.class, NumberPath.class, PathInits.DIRECT2); //inherited @@ -35,8 +37,6 @@ public class QChildProblem extends EntityPathBase { public final StringPath imageUrl = createString("imageUrl"); - public final EnumPath problemType = createEnum("problemType", com.moplus.moplus_server.domain.problem.domain.problem.ProblemType.class); - public final NumberPath sequence = createNumber("sequence", Integer.class); //inherited diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QDifficulty.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QDifficulty.java new file mode 100644 index 0000000..ccd4d1b --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QDifficulty.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QDifficulty is a Querydsl query type for Difficulty + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QDifficulty extends BeanPath { + + private static final long serialVersionUID = 175172936L; + + public static final QDifficulty difficulty1 = new QDifficulty("difficulty1"); + + public final NumberPath difficulty = createNumber("difficulty", Integer.class); + + public QDifficulty(String variable) { + super(Difficulty.class, forVariable(variable)); + } + + public QDifficulty(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QDifficulty(PathMetadata metadata) { + super(Difficulty.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java index fb35ae5..74bade6 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblem.java @@ -26,30 +26,36 @@ public class QProblem extends EntityPathBase { public final com.moplus.moplus_server.domain.problem.domain.QAnswer answer; - public final ListPath childProblems = this.createList("childProblems", com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem.class, com.moplus.moplus_server.domain.problem.domain.childProblem.QChildProblem.class, PathInits.DIRECT2); + public final EnumPath answerType = createEnum("answerType", AnswerType.class); - public final StringPath comment = createString("comment"); + public final ListPath childProblems = this.createList("childProblems", com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem.class, com.moplus.moplus_server.domain.problem.domain.childProblem.QChildProblem.class, PathInits.DIRECT2); public final SetPath> conceptTagIds = this.>createSet("conceptTagIds", Long.class, NumberPath.class, PathInits.DIRECT2); //inherited public final DateTimePath createdDate = _super.createdDate; - public final QProblemId id; + public final QDifficulty difficulty; - public final BooleanPath isPublished = createBoolean("isPublished"); + public final NumberPath id = createNumber("id", Long.class); - public final BooleanPath isVariation = createBoolean("isVariation"); + public final BooleanPath isConfirmed = createBoolean("isConfirmed"); public final StringPath mainAnalysisImageUrl = createString("mainAnalysisImageUrl"); + public final StringPath mainHandwritingExplanationImageUrl = createString("mainHandwritingExplanationImageUrl"); + public final StringPath mainProblemImageUrl = createString("mainProblemImageUrl"); + public final StringPath memo = createString("memo"); + public final NumberPath number = createNumber("number", Integer.class); public final NumberPath practiceTestId = createNumber("practiceTestId", Long.class); - public final StringPath prescriptionImageUrl = createString("prescriptionImageUrl"); + public final ListPath prescriptionImageUrls = this.createList("prescriptionImageUrls", String.class, StringPath.class, PathInits.DIRECT2); + + public final QProblemAdminId problemAdminId; public final EnumPath problemType = createEnum("problemType", ProblemType.class); @@ -57,6 +63,8 @@ public class QProblem extends EntityPathBase { public final StringPath seniorTipImageUrl = createString("seniorTipImageUrl"); + public final QTitle title; + //inherited public final DateTimePath updatedDate = _super.updatedDate; @@ -79,7 +87,9 @@ public QProblem(PathMetadata metadata, PathInits inits) { public QProblem(Class type, PathMetadata metadata, PathInits inits) { super(type, metadata, inits); this.answer = inits.isInitialized("answer") ? new com.moplus.moplus_server.domain.problem.domain.QAnswer(forProperty("answer")) : null; - this.id = inits.isInitialized("id") ? new QProblemId(forProperty("id")) : null; + this.difficulty = inits.isInitialized("difficulty") ? new QDifficulty(forProperty("difficulty")) : null; + this.problemAdminId = inits.isInitialized("problemAdminId") ? new QProblemAdminId(forProperty("problemAdminId")) : null; + this.title = inits.isInitialized("title") ? new QTitle(forProperty("title")) : null; } } diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java new file mode 100644 index 0000000..d7e56ee --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemAdminId.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QProblemAdminId is a Querydsl query type for ProblemAdminId + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QProblemAdminId extends BeanPath { + + private static final long serialVersionUID = 348147768L; + + public static final QProblemAdminId problemAdminId = new QProblemAdminId("problemAdminId"); + + public final StringPath id = createString("id"); + + public QProblemAdminId(String variable) { + super(ProblemAdminId.class, forVariable(variable)); + } + + public QProblemAdminId(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QProblemAdminId(PathMetadata metadata) { + super(ProblemAdminId.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java deleted file mode 100644 index ca7809a..0000000 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QProblemId.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.moplus.moplus_server.domain.problem.domain.problem; - -import static com.querydsl.core.types.PathMetadataFactory.*; - -import com.querydsl.core.types.dsl.*; - -import com.querydsl.core.types.PathMetadata; -import javax.annotation.processing.Generated; -import com.querydsl.core.types.Path; - - -/** - * QProblemId is a Querydsl query type for ProblemId - */ -@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") -public class QProblemId extends BeanPath { - - private static final long serialVersionUID = -1309260563L; - - public static final QProblemId problemId = new QProblemId("problemId"); - - public final StringPath id = createString("id"); - - public QProblemId(String variable) { - super(ProblemId.class, forVariable(variable)); - } - - public QProblemId(Path path) { - super(path.getType(), path.getMetadata()); - } - - public QProblemId(PathMetadata metadata) { - super(ProblemId.class, metadata); - } - -} - diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QTitle.java b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QTitle.java new file mode 100644 index 0000000..4c4cdd1 --- /dev/null +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/domain/problem/QTitle.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; + + +/** + * QTitle is a Querydsl query type for Title + */ +@Generated("com.querydsl.codegen.DefaultEmbeddableSerializer") +public class QTitle extends BeanPath { + + private static final long serialVersionUID = 42281131L; + + public static final QTitle title1 = new QTitle("title1"); + + public final StringPath title = createString("title"); + + public QTitle(String variable) { + super(Title.class, forVariable(variable)); + } + + public QTitle(Path<? extends Title> path) { + super(path.getType(), path.getMetadata()); + } + + public QTitle(PathMetadata metadata) { + super(Title.class, metadata); + } + +} + diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java index e764eac..dc056d0 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ChildProblemMapperImpl.java @@ -10,7 +10,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-08T16:27:45+0900", + date = "2025-02-12T02:55:03+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component @@ -25,7 +25,7 @@ public ChildProblem from(ChildProblemPostRequest request) { ChildProblem.ChildProblemBuilder childProblem = ChildProblem.builder(); childProblem.imageUrl( request.imageUrl() ); - childProblem.problemType( request.problemType() ); + childProblem.answerType( request.answerType() ); childProblem.answer( request.answer() ); Set<Long> set = request.conceptTagIds(); if ( set != null ) { @@ -45,7 +45,7 @@ public ChildProblem from(ChildProblemUpdateRequest request) { ChildProblem.ChildProblemBuilder childProblem = ChildProblem.builder(); childProblem.imageUrl( request.imageUrl() ); - childProblem.problemType( request.problemType() ); + childProblem.answerType( request.answerType() ); childProblem.answer( request.answer() ); Set<Long> set = request.conceptTagIds(); if ( set != null ) { diff --git a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java index 21c19b6..cf20a4a 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapperImpl.java @@ -2,72 +2,73 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; +import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import javax.annotation.processing.Generated; import org.springframework.stereotype.Component; @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-02-08T16:27:45+0900", + date = "2025-02-13T05:08:28+0900", comments = "version: 1.6.3, compiler: javac, environment: Java 17.0.10 (JetBrains s.r.o.)" ) @Component public class ProblemMapperImpl implements ProblemMapper { @Override - public Problem from(ProblemPostRequest request, ProblemId problemId, PracticeTestTag practiceTestTag) { - if ( request == null && problemId == null && practiceTestTag == null ) { + public Problem from(ProblemPostRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag) { + if ( request == null && problemAdminId == null && practiceTestTag == null ) { return null; } Problem.ProblemBuilder problem = Problem.builder(); if ( request != null ) { + problem.problemType( request.problemType() ); problem.number( request.number() ); - problem.answer( request.answer() ); - problem.comment( request.comment() ); - problem.mainProblemImageUrl( request.mainProblemImageUrl() ); - problem.mainAnalysisImageUrl( request.mainAnalysisImageUrl() ); - problem.readingTipImageUrl( request.readingTipImageUrl() ); - problem.seniorTipImageUrl( request.seniorTipImageUrl() ); - problem.prescriptionImageUrl( request.prescriptionImageUrl() ); - Set<Long> set = request.conceptTagIds(); - if ( set != null ) { - problem.conceptTagIds( new LinkedHashSet<Long>( set ) ); - } } - problem.id( problemId ); + problem.problemAdminId( problemAdminId ); problem.practiceTestTag( practiceTestTag ); return problem.build(); } @Override - public Problem from(ProblemUpdateRequest request, ProblemId problemId, PracticeTestTag practiceTestTag) { - if ( request == null && problemId == null && practiceTestTag == null ) { + public Problem from(ProblemUpdateRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag) { + if ( request == null && problemAdminId == null && practiceTestTag == null ) { return null; } Problem.ProblemBuilder problem = Problem.builder(); if ( request != null ) { - problem.answer( String.valueOf( request.answer() ) ); - problem.comment( request.comment() ); - problem.mainProblemImageUrl( request.mainProblemImageUrl() ); - problem.mainAnalysisImageUrl( request.mainAnalysisImageUrl() ); - problem.readingTipImageUrl( request.readingTipImageUrl() ); - problem.seniorTipImageUrl( request.seniorTipImageUrl() ); - problem.prescriptionImageUrl( request.prescriptionImageUrl() ); + problem.answerType( request.answerType() ); Set<Long> set = request.conceptTagIds(); if ( set != null ) { problem.conceptTagIds( new LinkedHashSet<Long>( set ) ); } + problem.difficulty( request.difficulty() ); + problem.mainHandwritingExplanationImageUrl( request.mainHandwritingExplanationImageUrl() ); + List<String> list = request.prescriptionImageUrls(); + if ( list != null ) { + problem.prescriptionImageUrls( new ArrayList<String>( list ) ); + } + problem.seniorTipImageUrl( request.seniorTipImageUrl() ); + problem.readingTipImageUrl( request.readingTipImageUrl() ); + problem.mainAnalysisImageUrl( request.mainAnalysisImageUrl() ); + problem.mainProblemImageUrl( request.mainProblemImageUrl() ); + problem.memo( request.memo() ); + problem.answer( request.answer() ); + problem.title( request.title() ); + problem.problemType( request.problemType() ); + problem.number( request.number() ); } - problem.id( problemId ); + problem.problemAdminId( problemAdminId ); problem.practiceTestTag( practiceTestTag ); return problem.build(); diff --git a/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java index 3f0c463..dc1cf58 100644 --- a/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java +++ b/src/main/generated/com/moplus/moplus_server/domain/problemset/domain/QProblemSet.java @@ -33,7 +33,7 @@ public class QProblemSet extends EntityPathBase<ProblemSet> { public final BooleanPath isDeleted = createBoolean("isDeleted"); - public final ListPath<com.moplus.moplus_server.domain.problem.domain.problem.ProblemId, com.moplus.moplus_server.domain.problem.domain.problem.QProblemId> problemIds = this.<com.moplus.moplus_server.domain.problem.domain.problem.ProblemId, com.moplus.moplus_server.domain.problem.domain.problem.QProblemId>createList("problemIds", com.moplus.moplus_server.domain.problem.domain.problem.ProblemId.class, com.moplus.moplus_server.domain.problem.domain.problem.QProblemId.class, PathInits.DIRECT2); + public final ListPath<Long, NumberPath<Long>> problemIds = this.<Long, NumberPath<Long>>createList("problemIds", Long.class, NumberPath.class, PathInits.DIRECT2); public final QTitle title; diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java index 48d6cfd..353a056 100644 --- a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java @@ -1,12 +1,10 @@ package com.moplus.moplus_server.domain.auth.controller; import com.moplus.moplus_server.domain.auth.dto.request.AdminLoginRequest; +import com.moplus.moplus_server.domain.auth.dto.response.TokenResponse; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.headers.Header; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,23 +16,12 @@ public class AuthController { @Operation(summary = "어드민 로그인", description = "아아디 패스워드 로그인 후 토큰 발급합니다.") - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "로그인 성공", - headers = { - @Header(name = "Authorization", description = "Access Token", schema = @Schema(type = "string")), - @Header(name = "RefreshToken", description = "Refresh Token", schema = @Schema(type = "string")) - } - ), - @ApiResponse(responseCode = "400", description = "잘못된 요청"), - @ApiResponse(responseCode = "401", description = "인증 실패") - }) @PostMapping("/admin/login") - public void adminLogin( + public ResponseEntity<TokenResponse> adminLogin( @RequestBody AdminLoginRequest request ) { // 실제 처리는 Security 필터에서 이루어지며, 이 메서드는 Swagger 명세용입니다. + return null; } - + } diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/TokenResponse.java b/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/TokenResponse.java new file mode 100644 index 0000000..a2be86d --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/auth/dto/response/TokenResponse.java @@ -0,0 +1,7 @@ +package com.moplus.moplus_server.domain.auth.dto.response; + +public record TokenResponse( + String accessToken, + String refreshToken +) { +} diff --git a/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java b/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java index 86e49c2..90d4c83 100644 --- a/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java +++ b/src/main/java/com/moplus/moplus_server/domain/concept/domain/ConceptTag.java @@ -6,12 +6,13 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ConceptTag extends BaseEntity { @Id diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java index 6772804..4d7f2fb 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/controller/ProblemController.java @@ -8,6 +8,7 @@ import com.moplus.moplus_server.domain.problem.service.ProblemSaveService; import com.moplus.moplus_server.domain.problem.service.ProblemUpdateService; import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -31,23 +32,23 @@ public class ProblemController { @GetMapping("/{id}") @Operation(summary = "문항 조회", description = "문항를 조회합니다.") public ResponseEntity<ProblemGetResponse> getProblem( - @PathVariable("id") String id + @PathVariable("id") Long id ) { return ResponseEntity.ok(problemGetService.getProblem(id)); } @PostMapping("") @Operation(summary = "문항 생성", description = "문제를 생성합니다. 새끼 문항은 list 순서대로 sequence를 저장합니다.") - public ResponseEntity<String> createProblem( - @RequestBody ProblemPostRequest request + public ResponseEntity<Long> createProblem( + @Valid @RequestBody ProblemPostRequest request ) { - return ResponseEntity.ok(problemSaveService.createProblem(request).toString()); + return ResponseEntity.ok(problemSaveService.createProblem(request)); } @PostMapping("/{id}") @Operation(summary = "문항 업데이트", description = "문제를 업데이트합니다. 문항 번호, 모의고사는 수정할 수 없습니다. 새로 추가되는 새끼문항 id는 빈 값입니다.") public ResponseEntity<ProblemGetResponse> updateProblem( - @PathVariable("id") String id, + @PathVariable("id") Long id, @RequestBody ProblemUpdateRequest request ) { return ResponseEntity.ok(problemUpdateService.updateProblem(id, request)); @@ -55,8 +56,8 @@ public ResponseEntity<ProblemGetResponse> updateProblem( @DeleteMapping("/{id}") @Operation(summary = "문항 삭제") - public ResponseEntity<ProblemGetResponse> updateProblem( - @PathVariable("id") String id + public ResponseEntity<Void> updateProblem( + @PathVariable("id") Long id ) { problemDeleteService.deleteProblem(id); return ResponseEntity.ok().body(null); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java index 2cb733a..79ec350 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/Answer.java @@ -1,6 +1,6 @@ package com.moplus.moplus_server.domain.problem.domain; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; import jakarta.persistence.Column; @@ -17,21 +17,21 @@ public class Answer { @Column(name = "answer") private String value; - public Answer(String value, ProblemType problemType) { - validateByType(value, problemType); + public Answer(String value, AnswerType answerType) { + if (value == null) { + return; + } + validateByType(value, answerType); this.value = value; } - private void validateByType(String answer, ProblemType problemType) { - if (answer.isBlank()) { - throw new InvalidValueException(ErrorCode.BLANK_INPUT_VALUE); - } - if (problemType == ProblemType.MULTIPLE_CHOICE) { + private void validateByType(String answer, AnswerType answerType) { + if (answerType == AnswerType.MULTIPLE_CHOICE) { if (!answer.matches("^[1-5]*$")) { throw new InvalidValueException(ErrorCode.INVALID_MULTIPLE_CHOICE_ANSWER); } } - if (problemType == ProblemType.SHORT_NUMBER_ANSWER) { + if (answerType == AnswerType.SHORT_NUMBER_ANSWER) { try { int numericAnswer = Integer.parseInt(answer); if (numericAnswer < 0 || numericAnswer > 999) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java index 2067862..f42c2d7 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/childProblem/ChildProblem.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.problem.domain.childProblem; import com.moplus.moplus_server.domain.problem.domain.Answer; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.global.common.BaseEntity; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; @@ -17,13 +17,14 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import java.util.Set; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class ChildProblem extends BaseEntity { @Id @@ -38,22 +39,22 @@ public class ChildProblem extends BaseEntity { @Embedded private Answer answer; @Enumerated(EnumType.STRING) - private ProblemType problemType; + private AnswerType answerType; private int sequence; @Builder - public ChildProblem(String imageUrl, ProblemType problemType, String answer, Set<Long> conceptTagIds, + public ChildProblem(String imageUrl, AnswerType answerType, String answer, Set<Long> conceptTagIds, int sequence) { - validateAnswerByType(answer, problemType); + validateAnswerByType(answer, answerType); this.imageUrl = imageUrl; - this.problemType = problemType; - this.answer = new Answer(answer, problemType); + this.answerType = answerType; + this.answer = new Answer(answer, answerType); this.conceptTagIds = conceptTagIds; this.sequence = sequence; } - public void validateAnswerByType(String answer, ProblemType problemType) { - if (this.problemType == ProblemType.MULTIPLE_CHOICE) { + public void validateAnswerByType(String answer, AnswerType answerType) { + if (this.answerType == AnswerType.MULTIPLE_CHOICE) { if (!answer.matches("^[1-5]*$")) { throw new InvalidValueException(ErrorCode.INVALID_MULTIPLE_CHOICE_ANSWER); } @@ -62,7 +63,7 @@ public void validateAnswerByType(String answer, ProblemType problemType) { public void update(ChildProblem input) { this.imageUrl = input.imageUrl; - this.problemType = input.problemType; + this.answerType = input.answerType; this.answer = input.answer; this.conceptTagIds = input.conceptTagIds; this.sequence = input.sequence; diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java index b21f78b..6f57f26 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/PracticeTestTag.java @@ -8,13 +8,14 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity @Table(name = "practice_test_tag") -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class PracticeTestTag { @Id diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java index 9dd4f16..75678f1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/practiceTest/Subject.java @@ -19,7 +19,7 @@ public enum Subject { private final String value; private final int problemCount; private final int perfectScore; - private final int idCode; + private final int code; public static Subject fromValue(String value) { return Arrays.stream(Subject.values()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java new file mode 100644 index 0000000..27d748d --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/AnswerType.java @@ -0,0 +1,37 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum AnswerType { + MULTIPLE_CHOICE("객관식"), + SHORT_NUMBER_ANSWER("주관식_숫자"), + SHORT_STRING_ANSWER("주관식_문자"); + + + private final String name; + + public static AnswerType getTypeForProblem(String subject, int number) { + + // 미적분, 기하, 확률과 통계 + if (subject.equals("미적분") || subject.equals("기하") || subject.equals("확률과통계")) { + if ((number >= 1 && number <= 15) || (number >= 23 && number <= 28)) { + return MULTIPLE_CHOICE; + } else if ((number >= 16 && number <= 22) || number == 29 || number == 30) { + return SHORT_NUMBER_ANSWER; + } + } + + // 고1, 고2 + if (subject.equals("고1") || subject.equals("고2")) { + if (number >= 1 && number <= 21) { + return MULTIPLE_CHOICE; + } else if (number >= 22 && number <= 30) { + return SHORT_NUMBER_ANSWER; + } + } + + // 기본값: 객관식 + return MULTIPLE_CHOICE; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java new file mode 100644 index 0000000..7cbcf54 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Difficulty.java @@ -0,0 +1,32 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Difficulty { + + @Column(name = "difficulty") + private Integer difficulty; + + public Difficulty(Integer difficulty) { + if (difficulty == null) { + return; + } + validate(difficulty); + this.difficulty = difficulty; + } + + private void validate(int difficulty) { + if (difficulty < 1 || difficulty > 10) { + throw new InvalidValueException(ErrorCode.INVALID_DIFFICULTY); + } + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java index e8c7814..bc3f96b 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Problem.java @@ -3,15 +3,21 @@ import com.moplus.moplus_server.domain.problem.domain.Answer; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; +import com.moplus.moplus_server.domain.problem.repository.converter.StringListConverter; import com.moplus.moplus_server.global.common.BaseEntity; import jakarta.persistence.CascadeType; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.ElementCollection; import jakarta.persistence.Embedded; -import jakarta.persistence.EmbeddedId; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToMany; import jakarta.persistence.OrderBy; @@ -20,35 +26,53 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Entity -@NoArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) public class Problem extends BaseEntity { - @EmbeddedId - ProblemId id; - + @Embedded + ProblemAdminId problemAdminId; Long practiceTestId; int number; + @Enumerated(EnumType.STRING) + ProblemType problemType; + @Embedded + Title title; @Embedded Answer answer; - String comment; + @Embedded + Difficulty difficulty; + + String memo; String mainProblemImageUrl; String mainAnalysisImageUrl; + String mainHandwritingExplanationImageUrl; String readingTipImageUrl; String seniorTipImageUrl; - String prescriptionImageUrl; + + @Convert(converter = StringListConverter.class) + @Column(columnDefinition = "TEXT") + List<String> prescriptionImageUrls; @ElementCollection @CollectionTable(name = "problem_concept", joinColumns = @JoinColumn(name = "problem_id")) @Column(name = "concept_tag_id") Set<Long> conceptTagIds; - private ProblemType problemType; - private boolean isPublished; - private boolean isVariation; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "problem_id") + private Long id; + + @Enumerated(EnumType.STRING) + private AnswerType answerType; + + private boolean isConfirmed; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name = "problem_id") @@ -56,24 +80,30 @@ public class Problem extends BaseEntity { private List<ChildProblem> childProblems = new ArrayList<>(); @Builder - public Problem(ProblemId id, PracticeTestTag practiceTestTag, int number, String answer, String comment, - String mainProblemImageUrl, - String mainAnalysisImageUrl, String readingTipImageUrl, String seniorTipImageUrl, - String prescriptionImageUrl, Set<Long> conceptTagIds) { - this.id = id; - this.practiceTestId = practiceTestTag.getId(); - this.number = number; - this.comment = comment; - this.mainProblemImageUrl = mainProblemImageUrl; - this.mainAnalysisImageUrl = mainAnalysisImageUrl; - this.readingTipImageUrl = readingTipImageUrl; + public Problem(List<ChildProblem> childProblems, boolean isConfirmed, AnswerType answerType, + Set<Long> conceptTagIds, Integer difficulty, String mainHandwritingExplanationImageUrl, + List<String> prescriptionImageUrls, String seniorTipImageUrl, String readingTipImageUrl, + String mainAnalysisImageUrl, String mainProblemImageUrl, String memo, String answer, String title, + ProblemType problemType, int number, PracticeTestTag practiceTestTag, + ProblemAdminId problemAdminId) { + this.childProblems = childProblems; + this.isConfirmed = isConfirmed; + this.answerType = answerType; + this.conceptTagIds = conceptTagIds; + this.mainHandwritingExplanationImageUrl = mainHandwritingExplanationImageUrl; + this.prescriptionImageUrls = prescriptionImageUrls; this.seniorTipImageUrl = seniorTipImageUrl; - this.prescriptionImageUrl = prescriptionImageUrl; - this.problemType = ProblemType.getTypeForProblem(practiceTestTag.getSubject().getValue(), number); - this.answer = new Answer(answer, this.problemType); - this.conceptTagIds = new HashSet<>(conceptTagIds); - this.isPublished = false; - this.isVariation = false; + this.readingTipImageUrl = readingTipImageUrl; + this.mainAnalysisImageUrl = mainAnalysisImageUrl; + this.mainProblemImageUrl = mainProblemImageUrl; + this.difficulty = new Difficulty(difficulty); + this.memo = memo; + this.answer = new Answer(answer, this.answerType); + this.title = new Title(title); + this.problemType = problemType; + this.number = number; + this.practiceTestId = practiceTestTag.getId(); + this.problemAdminId = problemAdminId; } public String getAnswer() { @@ -88,15 +118,23 @@ public void addChildProblem(List<ChildProblem> inputChildProblems) { } public void update(Problem inputProblem) { - this.conceptTagIds = new HashSet<>(inputProblem.getConceptTagIds()); + this.problemAdminId = inputProblem.getProblemAdminId(); + this.practiceTestId = inputProblem.getPracticeTestId(); this.number = inputProblem.getNumber(); - this.answer = new Answer(inputProblem.getAnswer(), this.problemType); - this.comment = inputProblem.getComment(); + this.problemType = inputProblem.getProblemType(); + this.title = new Title(inputProblem.getTitle()); + this.answer = new Answer(inputProblem.getAnswer(), inputProblem.getAnswerType()); + this.difficulty = new Difficulty(inputProblem.getDifficulty()); + this.conceptTagIds = new HashSet<>(inputProblem.getConceptTagIds()); + this.memo = inputProblem.getMemo(); this.mainProblemImageUrl = inputProblem.getMainProblemImageUrl(); this.mainAnalysisImageUrl = inputProblem.getMainAnalysisImageUrl(); + this.mainHandwritingExplanationImageUrl = inputProblem.getMainHandwritingExplanationImageUrl(); // 추가 this.readingTipImageUrl = inputProblem.getReadingTipImageUrl(); this.seniorTipImageUrl = inputProblem.getSeniorTipImageUrl(); - this.prescriptionImageUrl = inputProblem.getPrescriptionImageUrl(); + this.prescriptionImageUrls = inputProblem.getPrescriptionImageUrls(); + this.answerType = inputProblem.getAnswerType(); + this.isConfirmed = inputProblem.isConfirmed(); } public void updateChildProblem(List<ChildProblem> inputChildProblems) { @@ -122,13 +160,29 @@ public void deleteChildProblem(List<Long> deleteChildProblems) { } public boolean isValid() { - return answer != null && !answer.getValue().isEmpty() + return problemAdminId != null && practiceTestId != null - && comment != null && !comment.isEmpty() + && problemType != null + && title != null && !title.getTitle().isEmpty() + && answer != null && !answer.getValue().isEmpty() + && difficulty != null && difficulty.getDifficulty() != null + && memo != null && !memo.isEmpty() + && mainProblemImageUrl != null && !mainProblemImageUrl.isEmpty() + && mainAnalysisImageUrl != null && !mainAnalysisImageUrl.isEmpty() + && mainHandwritingExplanationImageUrl != null && !mainHandwritingExplanationImageUrl.isEmpty() && readingTipImageUrl != null && !readingTipImageUrl.isEmpty() && seniorTipImageUrl != null && !seniorTipImageUrl.isEmpty() - && prescriptionImageUrl != null && !prescriptionImageUrl.isEmpty() - && mainProblemImageUrl != null && !mainProblemImageUrl.isEmpty() - && mainAnalysisImageUrl != null && !mainAnalysisImageUrl.isEmpty(); + && prescriptionImageUrls != null && !prescriptionImageUrls.isEmpty() + && prescriptionImageUrls.stream().allMatch(url -> url != null && !url.isEmpty()) + && answerType != null + && conceptTagIds != null && !conceptTagIds.isEmpty(); + } + + public String getTitle() { + return title.getTitle(); + } + + public Integer getDifficulty() { + return difficulty.getDifficulty(); } } \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java similarity index 58% rename from src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java rename to src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java index 2bc4343..a30bb59 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemId.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminId.java @@ -3,18 +3,19 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import java.io.Serializable; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @Embeddable -@NoArgsConstructor -public class ProblemId implements Serializable { +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ProblemAdminId implements Serializable { - @Column(name = "problem_id") + @Column(name = "problem_admin_id") private String id; - public ProblemId(String id) { + public ProblemAdminId(String id) { this.id = id; } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java similarity index 54% rename from src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java rename to src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java index f3d3e82..8c61f9f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdService.java @@ -8,40 +8,37 @@ @Service @RequiredArgsConstructor -public class ProblemIdService { +public class ProblemAdminIdService { private static final AtomicInteger SEQUENCE = new AtomicInteger(1); // XXX 값 증가를 위한 카운터 private final ProblemRepository problemRepository; /* 문제 ID 생성 로직 + C : 문제 타입 ( 1: 기출문제, 2: 변형문제, 3: 창작문제 ) + S : ( 1: 고1, 2: 고2, 3: 미적분, 4: 기하, 5: 확률과 통계, 6: 가형, 7: 나형 ) YY: 년도 (두 자리) MM: 월 (두 자리) NN : 번호 (01~99) - AA : 영역 ( 01: 수학, 02: 영어, 03: 국어, 04: 사회, 05: 과학 ) - S : ( 1: 고1, 2: 고2, 3: 미적분, 4: 기하, 5: 확률과 통계, 6: 가형, 7: 나형 ) - C : 변형 여부 ( 0: 기본, 1: 변형 ) - XXX : 3자리 구분 숫자 + XX : 2자리 sequence 숫자 */ - public ProblemId nextId(int number, PracticeTestTag practiceTestTag) { + public ProblemAdminId nextId(int number, PracticeTestTag practiceTestTag, ProblemType problemType) { - int DEFAULT_AREA = 1; //현재 영역은 수학밖에 없음 - int subject = practiceTestTag.getSubject().getIdCode(); // AA (과목) + int problemTypeCode = problemType.getCode(); // C (문제 타입) + int subject = practiceTestTag.getSubject().getCode(); // S (과목) int year = practiceTestTag.getYear() % 100; // YY (두 자리 연도) int month = practiceTestTag.getMonth(); // MM (두 자리 월) - int DEFAULT_MODIFIED = 0; // 변형 여부 (0: 기본, 1: 변형) String generatedId; int sequence; // 중복되지 않는 ID 찾을 때까지 반복 do { - sequence = SEQUENCE.getAndIncrement() % 1000; // 000~999 순환 - generatedId = String.format("%02d%02d%02d%02d%d%d%03d", - year, month, number, DEFAULT_AREA, - subject, DEFAULT_MODIFIED, sequence); - } while (problemRepository.existsById(new ProblemId(generatedId))); // ID가 이미 존재하면 재생성 + sequence = SEQUENCE.getAndIncrement() % 100; // 000~999 순환 + generatedId = String.format("%d%d%02d%02d%02d%02d", + problemTypeCode, subject, year, month, number, sequence); + } while (problemRepository.existsByProblemAdminId(new ProblemAdminId(generatedId))); // ID가 이미 존재하면 재생성 - return new ProblemId(generatedId); + return new ProblemAdminId(generatedId); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java index 2410bcf..e02d101 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemType.java @@ -1,37 +1,16 @@ package com.moplus.moplus_server.domain.problem.domain.problem; +import lombok.Getter; import lombok.RequiredArgsConstructor; +@Getter @RequiredArgsConstructor public enum ProblemType { - MULTIPLE_CHOICE("객관식"), - SHORT_NUMBER_ANSWER("주관식_숫자"), - SHORT_STRING_ANSWER("주관식_문자"); + GICHUL_PROBLEM("기출문제", 1), + VARIANT_PROBLEM("변형문제", 2), + CREATION_PROBLEM("창작문제", 3); private final String name; - - public static ProblemType getTypeForProblem(String subject, int number) { - - // 미적분, 기하, 확률과 통계 - if (subject.equals("미적분") || subject.equals("기하") || subject.equals("확률과통계")) { - if ((number >= 1 && number <= 15) || (number >= 23 && number <= 28)) { - return MULTIPLE_CHOICE; - } else if ((number >= 16 && number <= 22) || number == 29 || number == 30) { - return SHORT_NUMBER_ANSWER; - } - } - - // 고1, 고2 - if (subject.equals("고1") || subject.equals("고2")) { - if (number >= 1 && number <= 21) { - return MULTIPLE_CHOICE; - } else if (number >= 22 && number <= 30) { - return SHORT_NUMBER_ANSWER; - } - } - - // 기본값: 객관식 - return MULTIPLE_CHOICE; - } + private final int code; } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Title.java b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Title.java new file mode 100644 index 0000000..2aa77dc --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/domain/problem/Title.java @@ -0,0 +1,26 @@ +package com.moplus.moplus_server.domain.problem.domain.problem; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Embeddable +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Title { + + private static final String DEFAULT_TITLE = "제목 없음"; + + @Column(name = "title") + private String title; + + public Title(String title) { + this.title = verifyTitle(title); + } + + private static String verifyTitle(String title) { + return (title == null || title.trim().isEmpty()) ? DEFAULT_TITLE : title; + } +} diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java index 1b42975..fc3e302 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemPostRequest.java @@ -1,11 +1,11 @@ package com.moplus.moplus_server.domain.problem.dto.request; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import java.util.Set; public record ChildProblemPostRequest( String imageUrl, - ProblemType problemType, + AnswerType answerType, String answer, Set<Long> conceptTagIds, int sequence diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java index d080b17..19ba8ff 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ChildProblemUpdateRequest.java @@ -1,6 +1,6 @@ package com.moplus.moplus_server.domain.problem.dto.request; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import io.swagger.v3.oas.annotations.media.Schema; import java.util.Set; @@ -8,7 +8,7 @@ public record ChildProblemUpdateRequest( @Schema(description = "새로 생성되는 새끼문항은 빈 값입니다.") Long id, String imageUrl, - ProblemType problemType, + AnswerType answerType, String answer, Set<Long> conceptTagIds, int sequence diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java index d6e5821..c909206 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemPostRequest.java @@ -2,36 +2,23 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; -import java.util.List; -import java.util.Set; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import jakarta.validation.constraints.NotNull; public record ProblemPostRequest( - Set<Long> conceptTagIds, + @NotNull(message = "문제 유형은 필수입니다") + ProblemType problemType, Long practiceTestId, - int number, - String answer, - String comment, - String mainProblemImageUrl, - String mainAnalysisImageUrl, - String readingTipImageUrl, - String seniorTipImageUrl, - String prescriptionImageUrl, - List<ChildProblemPostRequest> childProblems + int number ) { - public Problem toEntity(PracticeTestTag practiceTestTag, ProblemId problemId) { + public Problem toEntity(PracticeTestTag practiceTestTag, ProblemAdminId problemAdminId) { return Problem.builder() - .id(problemId) - .conceptTagIds(conceptTagIds) + .problemAdminId(problemAdminId) .practiceTestTag(practiceTestTag) .number(number) - .answer(answer) - .comment(comment) - .mainProblemImageUrl(mainProblemImageUrl) - .mainAnalysisImageUrl(mainAnalysisImageUrl) - .readingTipImageUrl(readingTipImageUrl) - .seniorTipImageUrl(seniorTipImageUrl) - .prescriptionImageUrl(prescriptionImageUrl) + .title("") + .problemType(problemType) .build(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java index 9e04f0d..1dcab23 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/request/ProblemUpdateRequest.java @@ -1,17 +1,26 @@ package com.moplus.moplus_server.domain.problem.dto.request; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import java.util.List; import java.util.Set; public record ProblemUpdateRequest( + ProblemType problemType, + Long practiceTestId, + int number, Set<Long> conceptTagIds, - int answer, - String comment, + String answer, + String title, + Integer difficulty, + String memo, String mainProblemImageUrl, String mainAnalysisImageUrl, + String mainHandwritingExplanationImageUrl, String readingTipImageUrl, String seniorTipImageUrl, - String prescriptionImageUrl, + List<String> prescriptionImageUrls, + AnswerType answerType, List<ChildProblemUpdateRequest> updateChildProblems, List<Long> deleteChildProblems ) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java index 1641d2c..ae7552a 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ChildProblemGetResponse.java @@ -1,7 +1,7 @@ package com.moplus.moplus_server.domain.problem.dto.response; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import java.util.Set; import lombok.Builder; @@ -9,7 +9,7 @@ public record ChildProblemGetResponse( Long childProblemId, String imageUrl, - ProblemType problemType, + AnswerType answerType, String answer, Set<Long> conceptTagIds ) { @@ -18,7 +18,7 @@ public static ChildProblemGetResponse of(ChildProblem childProblem) { return ChildProblemGetResponse.builder() .childProblemId(childProblem.getId()) .imageUrl(childProblem.getImageUrl()) - .problemType(childProblem.getProblemType()) + .answerType(childProblem.getAnswerType()) .answer(childProblem.getAnswer()) .conceptTagIds(childProblem.getConceptTagIds()) .build(); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java index 1947067..a39c30f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/dto/response/ProblemGetResponse.java @@ -1,6 +1,8 @@ package com.moplus.moplus_server.domain.problem.dto.response; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import java.util.List; import java.util.Set; import lombok.Builder; @@ -11,29 +13,39 @@ public record ProblemGetResponse( Set<Long> conceptTagIds, Long practiceTestId, int number, + Integer difficulty, + String title, String answer, - String comment, + String memo, + ProblemType problemType, + AnswerType answerType, String mainProblemImageUrl, + String mainHandwritingExplanationImageUrl, String mainAnalysisImageUrl, String readingTipImageUrl, String seniorTipImageUrl, - String prescriptionImageUrl, + List<String> prescriptionImageUrls, List<ChildProblemGetResponse> childProblems ) { public static ProblemGetResponse of(Problem problem) { return ProblemGetResponse.builder() - .problemId(problem.getId().toString()) + .problemId(problem.getProblemAdminId().getId()) .conceptTagIds(problem.getConceptTagIds()) .practiceTestId(problem.getPracticeTestId()) .number(problem.getNumber()) .answer(problem.getAnswer()) - .comment(problem.getComment()) + .title(problem.getTitle()) + .difficulty(problem.getDifficulty()) + .memo(problem.getMemo()) + .problemType(problem.getProblemType()) + .answerType(problem.getAnswerType()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) + .mainHandwritingExplanationImageUrl(problem.getMainHandwritingExplanationImageUrl()) .mainAnalysisImageUrl(problem.getMainAnalysisImageUrl()) .readingTipImageUrl(problem.getReadingTipImageUrl()) .seniorTipImageUrl(problem.getSeniorTipImageUrl()) - .prescriptionImageUrl(problem.getPrescriptionImageUrl()) + .prescriptionImageUrls(problem.getPrescriptionImageUrls()) .childProblems(problem.getChildProblems().stream() .map(ChildProblemGetResponse::of) .toList()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java index a45c8f4..04e7d94 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemRepository.java @@ -1,29 +1,22 @@ package com.moplus.moplus_server.domain.problem.repository; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; -import com.moplus.moplus_server.global.error.exception.AlreadyExistException; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.springframework.data.jpa.repository.JpaRepository; -public interface ProblemRepository extends JpaRepository<Problem, ProblemId> { +public interface ProblemRepository extends JpaRepository<Problem, Long> { - boolean existsByPracticeTestIdAndNumber(Long practiceTestId, int number); + boolean existsByProblemAdminId(ProblemAdminId problemAdminId); - default void existsByPracticeTestIdAndNumberOrThrow(Long practiceTestId, int number) { - if (existsByPracticeTestIdAndNumber(practiceTestId, number)) { - throw new AlreadyExistException(ErrorCode.PROBLEM_ALREADY_EXIST); - } - } - - default void existsByIdElseThrow(ProblemId problemId) { - if (!existsById(problemId)) { + default void existsByIdElseThrow(Long id) { + if (!existsById(id)) { throw new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND); } } - default Problem findByIdElseThrow(ProblemId problemId) { - return findById(problemId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); + default Problem findByIdElseThrow(Long id) { + return findById(id).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_NOT_FOUND)); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java index 97af1a4..b2ff859 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustom.java @@ -21,7 +21,7 @@ public class ProblemSearchRepositoryCustom { public List<ProblemSearchGetResponse> search(String problemId, String comment, List<Long> conceptTagIds) { return queryFactory - .select(problem.id.id, problem.comment, problem.mainProblemImageUrl) + .select(problem.problemAdminId.id, problem.memo, problem.mainProblemImageUrl) .from(problem) .where( containsProblemId(problemId), @@ -30,10 +30,10 @@ public List<ProblemSearchGetResponse> search(String problemId, String comment, L ) .leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)).fetchJoin() .distinct() - .transform(GroupBy.groupBy(problem.id.id).list( + .transform(GroupBy.groupBy(problem.id).list( Projections.constructor(ProblemSearchGetResponse.class, - problem.id.id, - problem.comment, + problem.problemAdminId.id, + problem.memo, problem.mainProblemImageUrl, GroupBy.set( Projections.constructor(ConceptTagSearchResponse.class, @@ -47,7 +47,8 @@ public List<ProblemSearchGetResponse> search(String problemId, String comment, L //problemId 일부 포함 검색 private BooleanExpression containsProblemId(String problemId) { - return (problemId == null || problemId.isEmpty()) ? null : problem.id.id.containsIgnoreCase(problemId); + return (problemId == null || problemId.isEmpty()) ? null + : problem.problemAdminId.id.containsIgnoreCase(problemId); } //name 조건 (포함 검색) @@ -55,7 +56,7 @@ private BooleanExpression containsName(String comment) { if (comment == null || comment.trim().isEmpty()) { return null; } - return problem.comment.containsIgnoreCase(comment.trim()); + return problem.memo.containsIgnoreCase(comment.trim()); } //conceptTagIds 조건 (하나라도 포함되면 조회) diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java b/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java new file mode 100644 index 0000000..9ca476b --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/domain/problem/repository/converter/StringListConverter.java @@ -0,0 +1,41 @@ +package com.moplus.moplus_server.domain.problem.repository.converter; + +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.error.exception.InvalidValueException; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Converter +public class StringListConverter implements AttributeConverter<List<String>, String> { + + private static final String DELIMITER = ", "; + + @Override + public String convertToDatabaseColumn(List<String> attribute) { + try { + if (attribute == null || attribute.isEmpty()) { + return ""; + } + return attribute.stream() + .filter(str -> str != null && !str.isEmpty()) + .collect(Collectors.joining(DELIMITER)); + } catch (Exception e) { + throw new InvalidValueException(ErrorCode.INVALID_INPUT_VALUE); + } + } + + @Override + public List<String> convertToEntityAttribute(String dbData) { + if (dbData == null || dbData.isEmpty()) { + return List.of(); + } + return Arrays.stream(dbData.split(DELIMITER)) + .filter(str -> str != null && !str.isEmpty()) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java index 1bb193f..dc9c92d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemDeleteService.java @@ -1,6 +1,5 @@ package com.moplus.moplus_server.domain.problem.service; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -13,8 +12,8 @@ public class ProblemDeleteService { private final ProblemRepository problemRepository; @Transactional - public void deleteProblem(String problemId) { - problemRepository.existsByIdElseThrow(new ProblemId(problemId)); - problemRepository.deleteById(new ProblemId(problemId)); + public void deleteProblem(Long id) { + problemRepository.existsByIdElseThrow(id); + problemRepository.deleteById(id); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java index 65dccf1..a3b0fa1 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemGetService.java @@ -1,7 +1,6 @@ package com.moplus.moplus_server.domain.problem.service; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import lombok.RequiredArgsConstructor; @@ -15,8 +14,8 @@ public class ProblemGetService { private final ProblemRepository problemRepository; @Transactional(readOnly = true) - public ProblemGetResponse getProblem(String problemId) { - Problem problem = problemRepository.findByIdElseThrow(new ProblemId(problemId)); + public ProblemGetResponse getProblem(Long problemId) { + Problem problem = problemRepository.findByIdElseThrow(problemId); return ProblemGetResponse.of(problem); } } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java index 6db9ddc..a6f6679 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveService.java @@ -1,17 +1,13 @@ package com.moplus.moplus_server.domain.problem.service; -import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; -import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemIdService; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminIdService; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; -import com.moplus.moplus_server.domain.problem.service.mapper.ChildProblemMapper; import com.moplus.moplus_server.domain.problem.service.mapper.ProblemMapper; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,24 +18,16 @@ public class ProblemSaveService { private final ProblemRepository problemRepository; private final PracticeTestTagRepository practiceTestRepository; - private final ConceptTagRepository conceptTagRepository; - private final ProblemIdService problemIdService; + private final ProblemAdminIdService problemAdminIdService; private final ProblemMapper problemMapper; - private final ChildProblemMapper childProblemMapper; @Transactional - public ProblemId createProblem(ProblemPostRequest request) { + public Long createProblem(ProblemPostRequest request) { PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); - problemRepository.existsByPracticeTestIdAndNumberOrThrow(practiceTestTag.getId(), request.number()); - conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); + ProblemAdminId problemAdminId = problemAdminIdService.nextId(request.number(), practiceTestTag, + request.problemType()); - ProblemId problemId = problemIdService.nextId(request.number(), practiceTestTag); - Problem problem = problemMapper.from(request, problemId, practiceTestTag); - - List<ChildProblem> childProblems = request.childProblems().stream() - .map(childProblemMapper::from) - .toList(); - problem.addChildProblem(childProblems); + Problem problem = problemMapper.from(request, problemAdminId, practiceTestTag); return problemRepository.save(problem).getId(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java index a7f0678..81f1086 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateService.java @@ -4,7 +4,8 @@ import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminIdService; import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; @@ -23,6 +24,7 @@ public class ProblemUpdateService { private final ProblemRepository problemRepository; + private final ProblemAdminIdService problemAdminIdService; private final PracticeTestTagRepository practiceTestRepository; private final ConceptTagRepository conceptTagRepository; private final ChildProblemRepository childProblemRepository; @@ -30,11 +32,15 @@ public class ProblemUpdateService { private final ProblemMapper problemMapper; @Transactional - public ProblemGetResponse updateProblem(String problemId, ProblemUpdateRequest request) { + public ProblemGetResponse updateProblem(Long problemId, ProblemUpdateRequest request) { + PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(request.practiceTestId()); conceptTagRepository.existsByIdElseThrow(request.conceptTagIds()); - Problem problem = problemRepository.findByIdElseThrow(new ProblemId(problemId)); - PracticeTestTag practiceTestTag = practiceTestRepository.findByIdElseThrow(problem.getPracticeTestId()); - Problem inputProblem = problemMapper.from(request, problem.getId(), practiceTestTag); + Problem problem = problemRepository.findByIdElseThrow(problemId); + + ProblemAdminId problemAdminId = problemAdminIdService.nextId(request.number(), practiceTestTag, + request.problemType()); + + Problem inputProblem = problemMapper.from(request, problemAdminId, practiceTestTag); problem.update(inputProblem); problem.deleteChildProblem(request.deleteChildProblems()); diff --git a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java index f1244a2..5497c41 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java +++ b/src/main/java/com/moplus/moplus_server/domain/problem/service/mapper/ProblemMapper.java @@ -2,7 +2,7 @@ import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import org.mapstruct.Mapper; @@ -13,14 +13,14 @@ public interface ProblemMapper { @Mappings({ - @Mapping(target = "id", source = "problemId"), + @Mapping(target = "problemAdminId", source = "problemAdminId"), @Mapping(target = "practiceTestTag", source = "practiceTestTag"), }) - Problem from(ProblemPostRequest request, ProblemId problemId, PracticeTestTag practiceTestTag); + Problem from(ProblemPostRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag); @Mappings({ - @Mapping(target = "id", source = "problemId"), + @Mapping(target = "problemAdminId", source = "problemAdminId"), @Mapping(target = "practiceTestTag", source = "practiceTestTag"), }) - Problem from(ProblemUpdateRequest request, ProblemId problemId, PracticeTestTag practiceTestTag); + Problem from(ProblemUpdateRequest request, ProblemAdminId problemAdminId, PracticeTestTag practiceTestTag); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java index bcd4d16..43d09db 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/domain/ProblemSet.java @@ -1,7 +1,6 @@ package com.moplus.moplus_server.domain.problemset.domain; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.global.common.BaseEntity; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; @@ -45,17 +44,17 @@ public class ProblemSet extends BaseEntity { @CollectionTable(name = "problem_set_problems", joinColumns = @JoinColumn(name = "problem_set_id")) @Column(name = "problem_id") @OrderColumn(name = "sequence") - private List<ProblemId> problemIds = new ArrayList<>(); + private List<Long> problemIds = new ArrayList<>(); @Builder - public ProblemSet(String title, List<ProblemId> problemIds) { + public ProblemSet(String title, List<Long> problemIds) { this.title = new Title(title); this.isDeleted = false; this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED; this.problemIds = problemIds; } - public void updateProblemOrder(List<ProblemId> newProblems) { + public void updateProblemOrder(List<Long> newProblems) { this.problemIds = new ArrayList<>(newProblems); } @@ -64,10 +63,10 @@ public void deleteProblemSet() { } public void toggleConfirm(List<Problem> problems) { - if(this.confirmStatus == ProblemSetConfirmStatus.NOT_CONFIRMED){ + if (this.confirmStatus == ProblemSetConfirmStatus.NOT_CONFIRMED) { List<String> invalidProblemIds = problems.stream() .filter(problem -> !problem.isValid()) - .map(problem -> problem.getId().getId()) + .map(problem -> problem.getProblemAdminId().getId()) .toList(); if (!invalidProblemIds.isEmpty()) { String message = ErrorCode.INVALID_CONFIRM_PROBLEM.getMessage() + @@ -78,7 +77,7 @@ public void toggleConfirm(List<Problem> problems) { this.confirmStatus = this.confirmStatus.toggle(); } - public void updateProblemSet(String title, List<ProblemId> newProblems) { + public void updateProblemSet(String title, List<Long> newProblems) { this.title = new Title(title); this.problemIds = newProblems; } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java index 602b249..e398789 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemReorderRequest.java @@ -3,6 +3,6 @@ import java.util.List; public record ProblemReorderRequest( - List<String> newProblems + List<Long> newProblems ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java index eb50f4d..d8d1abf 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetPostRequest.java @@ -1,14 +1,13 @@ package com.moplus.moplus_server.domain.problemset.dto.request; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import java.util.List; public record ProblemSetPostRequest( String problemSetTitle, - List<String> problems + List<Long> problems ) { - public ProblemSet toEntity(List<ProblemId> problemIdList) { + public ProblemSet toEntity(List<Long> problemIdList) { return ProblemSet.builder() .title(this.problemSetTitle) .problemIds(problemIdList) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java index 6752f0e..3cd3091 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/request/ProblemSetUpdateRequest.java @@ -4,6 +4,6 @@ public record ProblemSetUpdateRequest( String problemSetTitle, - List<String> problems + List<Long> problemIds ) { } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java index b563593..78ce956 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/dto/response/ProblemSummaryResponse.java @@ -9,16 +9,16 @@ public record ProblemSummaryResponse( String problemId, int number, String practiceTestName, - String comment, + String memo, String mainProblemImageUrl, List<String> tagNames ) { public static ProblemSummaryResponse of(Problem problem, String practiceTestName, List<String> tagNames) { return ProblemSummaryResponse.builder() - .problemId(problem.getId().toString()) + .problemId(problem.getProblemAdminId().getId()) .number(problem.getNumber()) - .comment(problem.getComment()) + .memo(problem.getMemo()) .mainProblemImageUrl(problem.getMainProblemImageUrl()) .practiceTestName(practiceTestName) .tagNames(tagNames) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java index 9bf6d22..7fc901c 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustom.java @@ -22,7 +22,8 @@ public class ProblemSetSearchRepositoryCustom { private final JPAQueryFactory queryFactory; - public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String problemTitle, List<String> conceptTagNames) { + public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String problemTitle, + List<String> conceptTagNames) { return queryFactory .from(problemSet) .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 @@ -48,7 +49,8 @@ public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String p )); } - public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, String problemTitle, List<String> conceptTagNames) { + public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, String problemTitle, + List<String> conceptTagNames) { return queryFactory .from(problemSet) .leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인 @@ -76,11 +78,12 @@ public List<ProblemSetSearchGetResponse> confirmSearch(String problemSetTitle, S } private BooleanExpression containsProblemSetTitle(String problemSetTitle) { - return (problemSetTitle == null || problemSetTitle.isEmpty()) ? null : problemSet.title.value.containsIgnoreCase(problemSetTitle); + return (problemSetTitle == null || problemSetTitle.isEmpty()) ? null + : problemSet.title.value.containsIgnoreCase(problemSetTitle); } private BooleanExpression containsProblemTitle(String problemTitle) { - return (problemTitle == null || problemTitle.isEmpty()) ? null : problem.comment.containsIgnoreCase(problemTitle); + return (problemTitle == null || problemTitle.isEmpty()) ? null : problem.memo.containsIgnoreCase(problemTitle); } private BooleanExpression containsConceptTagNames(List<String> conceptTagNames) { diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java index 3392a8d..16e1d2d 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetService.java @@ -4,7 +4,6 @@ import com.moplus.moplus_server.domain.concept.repository.ConceptTagRepository; import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; @@ -39,7 +38,7 @@ public ProblemSetGetResponse getProblemSet(Long problemSetId) { .orElse(null); List<ProblemSummaryResponse> problemSummaries = new ArrayList<>(); - for (ProblemId problemId : problemSet.getProblemIds()) { + for (Long problemId : problemSet.getProblemIds()) { Problem problem = problemRepository.findByIdElseThrow(problemId); PracticeTestTag practiceTestTag = practiceTestTagRepository.findByIdElseThrow(problem.getPracticeTestId()); List<String> tagNames = conceptTagRepository.findAllByIdsElseThrow(problem.getConceptTagIds()) diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java index c363815..6df9a8f 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetSaveService.java @@ -1,13 +1,11 @@ package com.moplus.moplus_server.domain.problemset.service; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest; import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.InvalidValueException; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,16 +24,11 @@ public Long createProblemSet(ProblemSetPostRequest request) { throw new InvalidValueException(ErrorCode.EMPTY_PROBLEMS_ERROR); } - // 문제 ID 리스트를 ProblemId 객체로 변환 - List<ProblemId> problemIdList = request.problems().stream() - .map(ProblemId::new) - .toList(); - // 모든 문항이 DB에 존재하는지 검증 - problemIdList.forEach(problemRepository::findByIdElseThrow); + request.problems().forEach(problemRepository::findByIdElseThrow); // ProblemSet 생성 - ProblemSet problemSet = request.toEntity(problemIdList); + ProblemSet problemSet = request.toEntity(request.problems()); return problemSetRepository.save(problemSet).getId(); } diff --git a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java index 92134ac..e588c82 100644 --- a/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java +++ b/src/main/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetUpdateService.java @@ -1,7 +1,6 @@ package com.moplus.moplus_server.domain.problemset.service; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus; @@ -12,7 +11,6 @@ import com.moplus.moplus_server.global.error.exception.InvalidValueException; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,12 +26,7 @@ public class ProblemSetUpdateService { public void reorderProblems(Long problemSetId, ProblemReorderRequest request) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); - // 기존 문항 ID 리스트 업데이트 (순서 반영) - List<ProblemId> updatedProblemIds = request.newProblems().stream() - .map(ProblemId::new) - .collect(Collectors.toList()); - - problemSet.updateProblemOrder(updatedProblemIds); + problemSet.updateProblemOrder(request.newProblems()); } @Transactional @@ -41,24 +34,20 @@ public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); // 빈 문항 유효성 검증 - if (request.problems().isEmpty()) { + if (request.problemIds().isEmpty()) { throw new InvalidValueException(ErrorCode.EMPTY_PROBLEMS_ERROR); } - // 문항 리스트 검증 - List<ProblemId> problemIdList = request.problems().stream() - .map(ProblemId::new) - .collect(Collectors.toList()); - problemIdList.forEach(problemRepository::findByIdElseThrow); + request.problemIds().forEach(problemRepository::findByIdElseThrow); - problemSet.updateProblemSet(request.problemSetTitle(), problemIdList); + problemSet.updateProblemSet(request.problemSetTitle(), request.problemIds()); } @Transactional public ProblemSetConfirmStatus toggleConfirmProblemSet(Long problemSetId) { ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId); List<Problem> problems = new ArrayList<>(); - for (ProblemId problemId : problemSet.getProblemIds()) { + for (Long problemId : problemSet.getProblemIds()) { Problem problem = problemRepository.findByIdElseThrow(problemId); problems.add(problem); } diff --git a/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java b/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java index de5fd60..9e163aa 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java +++ b/src/main/java/com/moplus/moplus_server/global/error/GlobalExceptionHandler.java @@ -3,10 +3,13 @@ import com.moplus.moplus_server.global.error.exception.BusinessException; import com.moplus.moplus_server.global.error.exception.ErrorCode; import com.moplus.moplus_server.global.error.exception.NotFoundException; +import java.util.List; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.transaction.CannotCreateTransactionException; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -25,6 +28,18 @@ protected ResponseEntity<ErrorResponse> handleBusinessException(final BusinessEx return new ResponseEntity<>(response, errorCode.getStatus()); } + @ExceptionHandler(MethodArgumentNotValidException.class) + protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) { + final List<String> errors = ex.getBindingResult() + .getAllErrors() + .stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .toList(); + + ErrorResponse response = ErrorResponse.from(ErrorCode.INVALID_INPUT_VALUE); + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } + @ExceptionHandler(NoHandlerFoundException.class) public ResponseEntity<String> handleNoHandlerFoundException(NoHandlerFoundException ex) { return new ResponseEntity<>("존재하지 않은 요청 엔드포인트입니다", HttpStatus.BAD_REQUEST); diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index 6b645b8..25f5519 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -33,6 +33,7 @@ public enum ErrorCode { INVALID_MULTIPLE_CHOICE_ANSWER(HttpStatus.BAD_REQUEST, "객관식 문제의 정답은 1~5 사이의 숫자여야 합니다"), INVALID_SHORT_NUMBER_ANSWER(HttpStatus.BAD_REQUEST, "주관식 문제의 정답은 0~999 사이의 숫자여야 합니다"), INVALID_CONFIRM_PROBLEM(HttpStatus.BAD_REQUEST, "유효하지 않은 문항들 : "), + INVALID_DIFFICULTY(HttpStatus.BAD_REQUEST, "난이도는 1~10 사이의 숫자여야 합니다"), //새끼 문항 CHILD_PROBLEM_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 새끼 문제를 찾을 수 없습니다"), diff --git a/src/main/resources/templates/answerInputForm.html b/src/main/resources/templates/answerInputForm.html index 6fd3d02..08e8d99 100644 --- a/src/main/resources/templates/answerInputForm.html +++ b/src/main/resources/templates/answerInputForm.html @@ -8,7 +8,7 @@ <script> function validatePoints() { let totalPoints = 0; - const radios = document.querySelectorAll('input[problemType="radio"]:checked'); + const radios = document.querySelectorAll('input[answerType="radio"]:checked'); radios.forEach(radio => { if (radio.name.startsWith('point_')) { @@ -30,7 +30,7 @@ <h2>답안 입력</h2> <form method="post" onsubmit="return validatePoints()" th:action="${problemForTests != null ? '/admin/practiceTests/submitAnswers/' + practiceTestId : '/admin/practiceTests/submitAnswers'}"> - <input name="practiceTestId" problemType="hidden" th:value="${practiceTestId}"> + <input answerType="hidden" name="practiceTestId" th:value="${practiceTestId}"> <table> <thead> <tr> @@ -46,38 +46,42 @@ <h2>답안 입력</h2> <td> <!-- subject가 미적분, 기하, 확률과통계일 경우의 배점 로직 --> <th:block th:if="${subject eq '미적분' || subject eq '기하' || subject eq '확률과통계'}"> - <input problemType="radio" th:checked="${#arrays.contains(new int[]{1, 2, 23}, i)}" + <input answerType="radio" th:checked="${#arrays.contains(new int[]{1, 2, 23}, i)}" th:id="'point_' + ${i} + '_2'" th:name="'point_' + ${i}" value="2"> <label th:for="'point_' + ${i} + '_2'">2점</label> - <input problemType="radio" - th:checked="${#arrays.contains(new int[]{3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 24, 25, 26, 27}, i)}" th:id="'point_' + ${i} + '_3'" th:name="'point_' + ${i}" + <input answerType="radio" + th:checked="${#arrays.contains(new int[]{3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 24, 25, 26, 27}, i)}" + th:id="'point_' + ${i} + '_3'" th:name="'point_' + ${i}" value="3"> <label th:for="'point_' + ${i} + '_3'">3점</label> - <input problemType="radio" - th:checked="${#arrays.contains(new int[]{9, 10, 11, 12, 13, 14, 15, 20, 21, 22, 28, 29, 30}, i)}" th:id="'point_' + ${i} + '_4'" th:name="'point_' + ${i}" + <input answerType="radio" + th:checked="${#arrays.contains(new int[]{9, 10, 11, 12, 13, 14, 15, 20, 21, 22, 28, 29, 30}, i)}" + th:id="'point_' + ${i} + '_4'" th:name="'point_' + ${i}" value="4"> <label th:for="'point_' + ${i} + '_4'">4점</label> </th:block> <!-- subject가 고1, 고2일 경우의 배점 로직 --> <th:block th:if="${subject eq '고1' || subject eq '고2'}"> - <input problemType="radio" th:checked="${#arrays.contains(new int[]{1, 2, 3}, i)}" + <input answerType="radio" th:checked="${#arrays.contains(new int[]{1, 2, 3}, i)}" th:id="'point_' + ${i} + '_2'" th:name="'point_' + ${i}" value="2"> <label th:for="'point_' + ${i} + '_2'">2점</label> - <input problemType="radio" - th:checked="${#arrays.contains(new int[]{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 23, 24, 25}, i)}" th:id="'point_' + ${i} + '_3'" th:name="'point_' + ${i}" + <input answerType="radio" + th:checked="${#arrays.contains(new int[]{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 23, 24, 25}, i)}" + th:id="'point_' + ${i} + '_3'" th:name="'point_' + ${i}" value="3"> <label th:for="'point_' + ${i} + '_3'">3점</label> - <input problemType="radio" - th:checked="${#arrays.contains(new int[]{14, 15, 16, 17, 18, 19, 20, 21, 26, 27, 28, 29, 30}, i)}" th:id="'point_' + ${i} + '_4'" th:name="'point_' + ${i}" + <input answerType="radio" + th:checked="${#arrays.contains(new int[]{14, 15, 16, 17, 18, 19, 20, 21, 26, 27, 28, 29, 30}, i)}" + th:id="'point_' + ${i} + '_4'" th:name="'point_' + ${i}" value="4"> <label th:for="'point_' + ${i} + '_4'">4점</label> </th:block> </td> - <input problemType="hidden" th:name="'answerFormat_' + ${i}" + <input answerType="hidden" th:name="'answerFormat_' + ${i}" th:value="${hasShortAnswer && i > 22 ? '단답식' : '객관식'}"> <!-- 문제 형식이 단답식인 경우 --> @@ -85,30 +89,35 @@ <h2>답안 입력</h2> <td th:if="${hasShortAnswer}"> <div th:if="${i <= 15 || (i >=23 && i<=28)}"> <!-- 첫 22개의 객관식 문항 --> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '1'}" th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '1'}" + th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" value="1"> <label th:for="'answer_' + ${i} + '_1'">1</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '2'}" th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '2'}" + th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" value="2"> <label th:for="'answer_' + ${i} + '_2'">2</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '3'}" th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '3'}" + th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" value="3"> <label th:for="'answer_' + ${i} + '_3'">3</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '4'}" th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '4'}" + th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" value="4"> <label th:for="'answer_' + ${i} + '_4'">4</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '5'}" th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '5'}" + th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" value="5"> <label th:for="'answer_' + ${i} + '_5'">5</label> </div> <div th:if="${(i >= 16 && i<= 22) || i == 29 || i == 30}"> <!-- 단답식 문항 --> - <input problemType="text" + <input answerType="text" th:name="'answer_' + ${i}" th:value="${problemForTests != null && problemForTests[i-1] != null ? problemForTests[i-1].answer : ''}"> </div> @@ -119,30 +128,35 @@ <h2>답안 입력</h2> <td th:if="${hasShortAnswer}"> <div th:if="${i <= 21}"> <!-- 첫 21개의 객관식 문항 --> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '1'}" th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '1'}" + th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" value="1"> <label th:for="'answer_' + ${i} + '_1'">1</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '2'}" th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '2'}" + th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" value="2"> <label th:for="'answer_' + ${i} + '_2'">2</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '3'}" th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '3'}" + th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" value="3"> <label th:for="'answer_' + ${i} + '_3'">3</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '4'}" th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '4'}" + th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" value="4"> <label th:for="'answer_' + ${i} + '_4'">4</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '5'}" th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1]?.answer == '5'}" + th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" value="5"> <label th:for="'answer_' + ${i} + '_5'">5</label> </div> <div th:if="${i >= 22 && i <= 30}"> <!-- 단답식 문항 --> - <input problemType="text" + <input answerType="text" th:name="'answer_' + ${i}" th:value="${problemForTests != null && problemForTests[i-1] != null ? problemForTests[i-1].answer : ''}"> </div> @@ -151,31 +165,36 @@ <h2>답안 입력</h2> <!-- 객관식만 있을 때 --> <td th:unless="${hasShortAnswer}"> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '1'}" th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '1'}" + th:id="'answer_' + ${i} + '_1'" th:name="'answer_' + ${i}" value="1"> <label th:for="'answer_' + ${i} + '_1'">1</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '2'}" th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '2'}" + th:id="'answer_' + ${i} + '_2'" th:name="'answer_' + ${i}" value="2"> <label th:for="'answer_' + ${i} + '_2'">2</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '3'}" th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '3'}" + th:id="'answer_' + ${i} + '_3'" th:name="'answer_' + ${i}" value="3"> <label th:for="'answer_' + ${i} + '_3'">3</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '4'}" th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '4'}" + th:id="'answer_' + ${i} + '_4'" th:name="'answer_' + ${i}" value="4"> <label th:for="'answer_' + ${i} + '_4'">4</label> - <input problemType="radio" - th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '5'}" th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" + <input answerType="radio" + th:checked="${problemForTests != null && problemForTests[i-1] != null && problemForTests[i-1].answer == '5'}" + th:id="'answer_' + ${i} + '_5'" th:name="'answer_' + ${i}" value="5"> <label th:for="'answer_' + ${i} + '_5'">5</label> </td> <!-- 정답률 입력 필드 --> <td> - <input placeholder="정답률 입력" problemType="text" style="width: 150px;" + <input answerType="text" placeholder="정답률 입력" style="width: 150px;" th:id="'correctRate_' + ${i}" th:name="'correctRate_' + ${i}" th:value="${problemForTests != null && problemForTests[i-1] != null ? problemForTests[i-1].correctRate : ''}"> @@ -183,7 +202,7 @@ <h2>답안 입력</h2> </tr> </tbody> </table> - <button problemType="submit">입력 완료</button> + <button answerType="submit">입력 완료</button> </form> </div> <script th:inline="javascript"> diff --git a/src/main/resources/templates/imageUploadPage.html b/src/main/resources/templates/imageUploadPage.html index 210cdd1..59fe602 100644 --- a/src/main/resources/templates/imageUploadPage.html +++ b/src/main/resources/templates/imageUploadPage.html @@ -22,7 +22,7 @@ <h2>문제 이미지 업로드</h2> <!-- 반복문으로 문제 리스트 출력 --> <th:block th:each="problemForTest : ${problemImageRequests}"> <tr> - <td th:text="${problemForTest.problemId}">문제 ID</td> + <td th:text="${problemForTest.problemAdminId}">문제 ID</td> <td th:text="${problemForTest.problemNumber}">문제 번호</td> <td> <!-- 이미지 URL이 있는 경우 이미지를 표시 --> @@ -34,10 +34,10 @@ <h2>문제 이미지 업로드</h2> <td> <!-- 업로드 버튼 --> <form enctype="multipart/form-data" method="post" - th:action="'/admin/practiceTests/uploadImage/' + ${problemForTest.problemId}"> - <input accept="image/*" name="image" problemType="file"> - <input name="practiceTestId" problemType="hidden" th:value="${practiceTestId}"> - <button class="upload-button" problemType="submit">업로드</button> + th:action="'/admin/practiceTests/uploadImage/' + ${problemForTest.problemAdminId}"> + <input accept="image/*" answerType="file" name="image"> + <input answerType="hidden" name="practiceTestId" th:value="${practiceTestId}"> + <button answerType="submit" class="upload-button">업로드</button> </form> </td> </tr> @@ -47,7 +47,7 @@ <h2>문제 이미지 업로드</h2> <!-- 완료 버튼 --> <div class="complete-button-container"> <form action="/practiceTests" method="get"> - <button class="complete-button" problemType="submit">완료</button> + <button answerType="submit" class="complete-button">완료</button> </form> </div> </div> diff --git a/src/main/resources/templates/practiceTestList.html b/src/main/resources/templates/practiceTestList.html index f3ef96f..dab424d 100644 --- a/src/main/resources/templates/practiceTestList.html +++ b/src/main/resources/templates/practiceTestList.html @@ -34,8 +34,8 @@ <body> <div class="container"> <div class="search-bar"> - <input id="search-input" onkeyup="filterTests()" placeholder="검색" problemType="text"> - <button problemType="button"> + <input answerType="text" id="search-input" onkeyup="filterTests()" placeholder="검색"> + <button answerType="button"> <img alt="검색" src="/images/search_icon.png"> </button> </div> @@ -57,8 +57,8 @@ </a> </div> <form class="delete-form" method="post" th:action="@{'/admin/practiceTests/' + ${test.id} + '/delete'}"> - <input name="_method" problemType="hidden" value="delete"> - <button class="delete-test-button" onclick="confirmDeletion(event)" problemType="button">삭제</button> + <input answerType="hidden" name="_method" value="delete"> + <button answerType="button" class="delete-test-button" onclick="confirmDeletion(event)">삭제</button> </form> </div> </li> diff --git a/src/main/resources/templates/testInputForm.html b/src/main/resources/templates/testInputForm.html index 1666a8a..c49358d 100644 --- a/src/main/resources/templates/testInputForm.html +++ b/src/main/resources/templates/testInputForm.html @@ -19,7 +19,7 @@ font-weight: bold; } - input[problemType="text"] { + input[answerType="text"] { width: 100%; padding: 8px; box-sizing: border-box; @@ -48,7 +48,7 @@ } /* Radio 버튼 숨기기 */ - .subject-item input[problemType="radio"] { + .subject-item input[answerType="radio"] { display: none; } @@ -64,7 +64,7 @@ } /* 선택된 라디오 버튼 스타일 */ - .subject-item input[problemType="radio"]:checked + label { + .subject-item input[answerType="radio"]:checked + label { background-color: #f79a3e; color: white; border-color: #f47c20; @@ -106,25 +106,26 @@ <h1>Practice Test 등록</h1> <form method="post" - th:action="${practiceTestRequest.id != null ? '/admin/practiceTests/submit/' + practiceTestRequest.id : '/admin/practiceTests/submit'}" th:object="${practiceTestRequest}"> + th:action="${practiceTestRequest.id != null ? '/admin/practiceTests/submit/' + practiceTestRequest.id : '/admin/practiceTests/submit'}" + th:object="${practiceTestRequest}"> <div class="form-group"> <label for="name">이름</label> - <input id="name" placeholder="이름" problemType="text" th:field="*{name}"> + <input answerType="text" id="name" placeholder="이름" th:field="*{name}"> </div> <div class="form-group"> <label for="round">회차</label> - <input id="round" placeholder="회차" problemType="text" th:field="*{round}"> + <input answerType="text" id="round" placeholder="회차" th:field="*{round}"> </div> <div class="form-group"> <label for="provider">출제기관</label> - <input id="provider" placeholder="출제기관" problemType="text" th:field="*{provider}"> + <input answerType="text" id="provider" placeholder="출제기관" th:field="*{provider}"> </div> <div class="form-group"> <label for="provider">출제 연도</label> - <input id="publicationYear" placeholder="출제 연도" problemType="text" th:field="*{publicationYear}"> + <input answerType="text" id="publicationYear" placeholder="출제 연도" th:field="*{publicationYear}"> </div> <div class="form-group"> @@ -132,7 +133,7 @@ <h1>Practice Test 등록</h1> <div class="subject-container"> <div class="subject-item" th:each="subject, iterStat : ${subjects}"> <!-- 고유한 id와 for 값을 설정 --> - <input problemType="radio" th:field="*{subject}" th:id="'subject' + ${iterStat.index}" + <input answerType="radio" th:field="*{subject}" th:id="'subject' + ${iterStat.index}" th:onchange="'showTable(' + ${iterStat.index} + ')'" th:value="${subject}"/> <label th:for="'subject' + ${iterStat.index}" th:text="${subject.getValue()}"></label> @@ -150,9 +151,10 @@ <h3 th:text="${subject.getValue()}"></h3> <div th:each="ratingTable, ratingTableStat : ${practiceTestRequest.ratingTables}"> <!-- Provider 이름 표시 행 --> <th colspan="4"> - <input placeholder="Rating Provider" - problemType="text" - th:field="*{ratingTables[__${ratingTableStat.index}__].ratingProvider}" th:value="${ratingTable.ratingProvider}"/> + <input answerType="text" + placeholder="Rating Provider" + th:field="*{ratingTables[__${ratingTableStat.index}__].ratingProvider}" + th:value="${ratingTable.ratingProvider}"/> </th> <tr> <th>등급</th> @@ -163,19 +165,23 @@ <h3 th:text="${subject.getValue()}"></h3> <tr th:each="ratingRow, ratingRowStat : ${ratingTable.ratingRows}" th:if="${ratingTable.ratingRows != null and !#lists.isEmpty(ratingTable.ratingRows)}"> <td> - <input problemType="text" + <input answerType="text" readonly - th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].rating}" th:value="${ratingRow.rating}"/> + th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].rating}" + th:value="${ratingRow.rating}"/> + </td> + <td><input answerType="text" + placeholder="원점수" + th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].rawScores}"> + </td> + <td><input answerType="text" + placeholder="표준점수" + th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].standardScores}"> + </td> + <td><input answerType="text" + placeholder="백분위" + th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].percentiles}"> </td> - <td><input placeholder="원점수" - problemType="text" - th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].rawScores}"></td> - <td><input placeholder="표준점수" - problemType="text" - th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].standardScores}"></td> - <td><input placeholder="백분위" - problemType="text" - th:field="*{ratingTables[__${ratingTableStat.index}__].ratingRows[__${ratingRowStat.index}__].percentiles}"></td> </tr> </div> </tbody> @@ -184,7 +190,7 @@ <h3 th:text="${subject.getValue()}"></h3> </div> </div> - <button problemType="submit">선택완료</button> + <button answerType="submit">선택완료</button> </form> <script> @@ -209,7 +215,7 @@ <h3 th:text="${subject.getValue()}"></h3> // 페이지가 로드되었을 때 선택된 과목이 있으면 해당 테이블 표시 document.addEventListener('DOMContentLoaded', function () { - const selectedRadio = document.querySelector('.subject-item input[problemType="radio"]:checked'); + const selectedRadio = document.querySelector('.subject-item input[answerType="radio"]:checked'); if (selectedRadio) { showTable(selectedRadio.id.replace('subject', '')); } diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java similarity index 64% rename from src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java rename to src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java index 629e128..184e71c 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemIdServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/domain/problem/ProblemAdminIdServiceTest.java @@ -19,13 +19,14 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class ProblemIdServiceTest { +class ProblemAdminIdServiceTest { + public static final String ID_LENGTH = "10"; @Mock private ProblemRepository problemRepository; @InjectMocks - private ProblemIdService problemIdService; + private ProblemAdminIdService problemAdminIdService; private PracticeTestTag practiceTestTag; @@ -41,18 +42,19 @@ void setUp() { void nextId_정상생성_및_중복확인() { // given int 문제번호 = 20; - when(problemRepository.existsById(any(ProblemId.class))).thenReturn(false); // 중복 없음 + ProblemType problemType = ProblemType.GICHUL_PROBLEM; + when(problemRepository.existsByProblemAdminId(any())).thenReturn(false); // 중복 없음 // when - ProblemId generatedId = problemIdService.nextId(문제번호, practiceTestTag); + ProblemAdminId generatedId = problemAdminIdService.nextId(문제번호, practiceTestTag, problemType); // then assertThat(generatedId).isNotNull(); - assertThat(generatedId.getId()).matches("\\d{13}"); // ID 형식이 맞는지 확인 - assertThat(generatedId.getId()).startsWith("2405200120"); + assertThat(generatedId.getId()).matches("\\d{" + ID_LENGTH + "}"); // ID 형식이 맞는지 확인 + assertThat(generatedId.getId()).startsWith("12240520"); // 문제 ID 중복 확인을 위해 existsById 호출 확인 - verify(problemRepository, atLeastOnce()).existsById(any(ProblemId.class)); + verify(problemRepository, atLeastOnce()).existsByProblemAdminId(any()); } @@ -60,19 +62,20 @@ void setUp() { void nextId_중복발생시_다시_생성() { // given int 문제번호 = 2; - when(problemRepository.existsById(any(ProblemId.class))) + ProblemType problemType = ProblemType.GICHUL_PROBLEM; + when(problemRepository.existsByProblemAdminId(any())) .thenReturn(true) // 첫 번째 생성된 ID는 중복됨 .thenReturn(false); // 두 번째는 중복 없음 // when - ProblemId generatedId = problemIdService.nextId(문제번호, practiceTestTag); + ProblemAdminId generatedId = problemAdminIdService.nextId(문제번호, practiceTestTag, problemType); // then assertThat(generatedId).isNotNull(); - assertThat(generatedId.getId()).matches("\\d{13}"); - assertThat(generatedId.getId()).startsWith("2405020120"); + assertThat(generatedId.getId()).matches("\\d{" + ID_LENGTH + "}"); + assertThat(generatedId.getId()).startsWith("12240502"); // 중복된 ID가 나왔으므로 existsById가 최소 두 번 이상 호출되었는지 확인 - verify(problemRepository, atLeast(2)).existsById(any(ProblemId.class)); + verify(problemRepository, atLeast(2)).existsByProblemAdminId(any()); } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java index 712115d..4eb1e25 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/repository/ProblemSearchRepositoryCustomTest.java @@ -23,12 +23,12 @@ public class ProblemSearchRepositoryCustomTest { @Test void problemId_일부_포함_검색() { // when - List<ProblemSearchGetResponse> result = problemSearchRepository.search("240520012", null, null); + List<ProblemSearchGetResponse> result = problemSearchRepository.search("12240520", null, null); // then assertThat(result).hasSize(2); assertThat(result).extracting(ProblemSearchGetResponse::getProblemId) - .containsExactlyInAnyOrder("240520012001", "240520012002"); + .containsExactlyInAnyOrder("1224052001", "1224052002"); } @Test @@ -38,7 +38,7 @@ public class ProblemSearchRepositoryCustomTest { // then assertThat(result).hasSize(1); - assertThat(result.get(0).getProblemId()).isEqualTo("240520012001"); + assertThat(result.get(0).getProblemId()).isEqualTo("1224052001"); } @Test @@ -49,17 +49,17 @@ public class ProblemSearchRepositoryCustomTest { // then assertThat(result).hasSize(2); assertThat(result).extracting(ProblemSearchGetResponse::getProblemId) - .containsExactlyInAnyOrder("240520012001", "240520012002"); + .containsExactlyInAnyOrder("1224052001", "1224052002"); } @Test void problemId_이름_conceptTagIds_모두_적용된_검색() { // when - List<ProblemSearchGetResponse> result = problemSearchRepository.search("2405200120", "설명 1", List.of(1L)); + List<ProblemSearchGetResponse> result = problemSearchRepository.search("12240520", "설명 1", List.of(1L)); // then assertThat(result).hasSize(1); - assertThat(result.get(0).getProblemId()).isEqualTo("240520012001"); + assertThat(result.get(0).getProblemId()).isEqualTo("1224052001"); } @Test diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java index 63abbe6..21b2a2f 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemSaveServiceTest.java @@ -1,18 +1,15 @@ package com.moplus.moplus_server.domain.problem.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; -import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemPostRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; -import java.util.List; -import java.util.Set; -import java.util.stream.IntStream; +import com.moplus.moplus_server.global.error.exception.NotFoundException; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -32,117 +29,46 @@ class ProblemSaveServiceTest { @Autowired private ProblemRepository problemRepository; - private ProblemPostRequest problemPostRequestOutOfOrder; - private ProblemPostRequest problemPostRequestInOrder; + private ProblemPostRequest problemPostRequest; @BeforeEach void setUp() { - // 🔹 1. 일부러 순서를 뒤죽박죽으로 설정한 문제 - ChildProblemPostRequest childProblem1 = new ChildProblemPostRequest( - "child1.png", ProblemType.SHORT_STRING_ANSWER, "정답1", Set.of(3L, 4L), 3 - ); - ChildProblemPostRequest childProblem2 = new ChildProblemPostRequest( - "child2.png", ProblemType.MULTIPLE_CHOICE, "1", Set.of(5L, 6L), 1 - ); - ChildProblemPostRequest childProblem3 = new ChildProblemPostRequest( - "child3.png", ProblemType.MULTIPLE_CHOICE, "2", Set.of(3L, 4L), 0 - ); - ChildProblemPostRequest childProblem4 = new ChildProblemPostRequest( - "child4.png", ProblemType.SHORT_NUMBER_ANSWER, "0", Set.of(1L, 2L), 2 - ); - - problemPostRequestOutOfOrder = new ProblemPostRequest( - Set.of(1L, 2L), - 1L, - 21, - "1", - "설명", - "mainProblem.png", - "mainAnalysis.png", - "readingTip.png", - "seniorTip.png", - "prescription.png", - List.of(childProblem1, childProblem2, childProblem3, childProblem4) // 🔹 순서 뒤죽박죽 - ); - - // 🔹 2. 순서가 올바른 상태에서 입력되는 문제 - problemPostRequestInOrder = new ProblemPostRequest( - Set.of(1L, 2L), + problemPostRequest = new ProblemPostRequest( + ProblemType.GICHUL_PROBLEM, 1L, - 20, - "2", - "다른 설명", - "mainProblem2.png", - "mainAnalysis2.png", - "readingTip2.png", - "seniorTip2.png", - "prescription2.png", - List.of(childProblem3, childProblem2, childProblem4, childProblem1) // 🔹 순서 유지 (0,1,2,3) + 20 ); - } - @Test - void 정상동작() { - - // when - ProblemId createdProblemId = problemSaveService.createProblem(problemPostRequestInOrder); - - // then - assertThat(createdProblemId).isNotNull(); - assertThat(createdProblemId.getId()).startsWith("2405200120"); // ID 앞부분 확인 - - Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemId); - - // 모든 자식 문제의 conceptTagIds가 부모 문제의 conceptTagIds에 포함되는지 검증 - Set<Long> problemTags = savedProblem.getConceptTagIds(); - problemPostRequestInOrder.childProblems().forEach(child -> { - assertThat(problemTags).containsAll(child.conceptTagIds()); - }); - - // 자식 문제의 순서 검증 - List<ChildProblem> childProblems = savedProblem.getChildProblems(); - - assertThat(childProblems).hasSize(4); // 자식 문제 개수 검증 - - // 저장된 자식 문제가 원래 요청한 `sequence` 순서와 같은지 확인 - IntStream.range(0, childProblems.size()).forEach(i -> { - assertThat(childProblems.get(i).getSequence()).isEqualTo(i); - }); + @Nested + class 문항생성 { + + @Test + void 성공() { + // when + Long createdProblemId = problemSaveService.createProblem(problemPostRequest); + + // then + assertThat(createdProblemId).isNotNull(); + + Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemId); + assertThat(savedProblem).isNotNull(); + assertThat(savedProblem.getProblemType()).isEqualTo(ProblemType.GICHUL_PROBLEM); + } + + @Test + void 문제_저장_실패_잘못된_부모_문제_ID() { + // given + ProblemPostRequest invalidParentRequest = new ProblemPostRequest( + ProblemType.GICHUL_PROBLEM, + 999L, // 존재하지 않는 parentId 사용 + 10 + ); + + // when & then + assertThatThrownBy(() -> problemSaveService.createProblem(invalidParentRequest)) + .isInstanceOf(NotFoundException.class); + } } - @Test - void 자식문제_올바른_순서_저장() { - // when - ProblemId createdProblemId = problemSaveService.createProblem(problemPostRequestOutOfOrder); - - // then - assertThat(createdProblemId).isNotNull(); - assertThat(createdProblemId.getId()).startsWith("2405210120"); // ID 앞부분 확인 - - // 저장된 문제 조회 - Problem savedProblem = problemRepository.findByIdElseThrow(createdProblemId); - - // ✅ 모든 자식 문제의 conceptTagIds가 부모 문제의 conceptTagIds에 포함되는지 검증 - Set<Long> problemTags = savedProblem.getConceptTagIds(); - problemPostRequestOutOfOrder.childProblems().forEach(child -> { - assertThat(problemTags).containsAll(child.conceptTagIds()); - }); - - // ✅ 자식 문제의 순서 검증 - List<ChildProblem> childProblems = savedProblem.getChildProblems(); - - assertThat(childProblems).hasSize(4); // 자식 문제 개수 검증 - - // 🔹 저장된 자식 문제들이 `sequence` 오름차순으로 정렬되었는지 확인 - IntStream.range(0, childProblems.size()).forEach(i -> { - assertThat(childProblems.get(i).getSequence()).isEqualTo(i); - }); - - // 🔹 정렬 후 올바른 문제인지 검증 - assertThat(childProblems.get(0).getImageUrl()).isEqualTo("child3.png"); // sequence 0 - assertThat(childProblems.get(1).getImageUrl()).isEqualTo("child2.png"); // sequence 1 - assertThat(childProblems.get(2).getImageUrl()).isEqualTo("child4.png"); // sequence 2 - assertThat(childProblems.get(3).getImageUrl()).isEqualTo("child1.png"); // sequence 3 - } } \ No newline at end of file diff --git a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java index 6d59ad5..6760214 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problem/service/ProblemUpdateServiceTest.java @@ -1,19 +1,23 @@ package com.moplus.moplus_server.domain.problem.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.moplus.moplus_server.domain.problem.domain.childProblem.ChildProblem; +import com.moplus.moplus_server.domain.problem.domain.problem.AnswerType; import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; +import com.moplus.moplus_server.domain.problem.domain.problem.ProblemAdminId; import com.moplus.moplus_server.domain.problem.domain.problem.ProblemType; import com.moplus.moplus_server.domain.problem.dto.request.ChildProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.request.ProblemUpdateRequest; import com.moplus.moplus_server.domain.problem.dto.response.ProblemGetResponse; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; +import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.util.List; import java.util.Set; import java.util.stream.IntStream; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -33,80 +37,166 @@ class ProblemUpdateServiceTest { @Autowired private ProblemRepository problemRepository; - private ProblemId problemId; + private ProblemAdminId problemAdminId; private ProblemUpdateRequest problemUpdateRequest; @BeforeEach void setUp() { - problemId = new ProblemId("240520012001"); + problemAdminId = new ProblemAdminId("240520012001"); - // 🔹 새 자식 문제 추가 + // 새 자식 문제 추가 ChildProblemUpdateRequest newChildProblem = new ChildProblemUpdateRequest( null, "newChild.png", - ProblemType.SHORT_STRING_ANSWER, + AnswerType.SHORT_STRING_ANSWER, "새로운 정답", Set.of(1L, 2L), 1 ); - // 🔹 기존 자식 문제 업데이트 + // 기존 자식 문제 업데이트 ChildProblemUpdateRequest updateChildProblem = new ChildProblemUpdateRequest( 1L, // 기존 자식 문제 ID "updatedChild.png", - ProblemType.MULTIPLE_CHOICE, + AnswerType.MULTIPLE_CHOICE, "2", Set.of(2L, 3L), 0 ); - // 🔹 기존 자식 문제 삭제 + // 기존 자식 문제 삭제 List<Long> deleteChildProblem = List.of(2L); // 삭제할 자식 문제 ID problemUpdateRequest = new ProblemUpdateRequest( - Set.of(1L, 2L, 3L), // 업데이트할 부모 문제의 Concept Tags - 1, // 문제 정답 - "수정된 설명", // 새로운 설명 + ProblemType.VARIANT_PROBLEM, + 2L, + 10, + Set.of(1L, 2L, 3L), + "정답", + "업데이트된 제목", + 3, + "업데이트된 메모", "updatedMainProblem.png", "updatedMainAnalysis.png", + "updatedMainHandwriting.png", // 추가 "updatedReadingTip.png", "updatedSeniorTip.png", - "updatedPrescription.png", - List.of(newChildProblem, updateChildProblem), // 업데이트할 자식 문제 - deleteChildProblem // 삭제할 자식 문제 + List.of("prescription1.png", "prescription2.png"), // List<String>으로 변경 + AnswerType.SHORT_STRING_ANSWER, + List.of(newChildProblem, updateChildProblem), + deleteChildProblem ); } - @Test - void 문제_업데이트_정상동작() { - // when - ProblemGetResponse response = problemUpdateService.updateProblem(problemId.getId(), - problemUpdateRequest); - - // then - assertThat(response).isNotNull(); - assertThat(response.comment()).isEqualTo("수정된 설명"); // ✅ 설명이 변경되었는지 검증 - assertThat(response.mainProblemImageUrl()).isEqualTo("updatedMainProblem.png"); // ✅ 이미지 URL 변경 확인 - - Problem updatedProblem = problemRepository.findByIdElseThrow(problemId); - - // ✅ 자식 문제 개수 검증 - List<ChildProblem> childProblems = updatedProblem.getChildProblems(); - assertThat(childProblems).hasSize(2); // 기존 2개 → 1개 삭제, 1개 추가 후 2개 - - // ✅ 부모 문제의 conceptTagIds가 자식 문제의 conceptTagIds를 모두 포함하는지 검증 - Set<Long> problemTags = updatedProblem.getConceptTagIds(); - updatedProblem.getChildProblems().forEach(child -> { - assertThat(problemTags).containsAll(child.getConceptTagIds()); - }); - - // ✅ 자식 문제 순서가 올바르게 정렬되었는지 확인 - IntStream.range(0, childProblems.size()).forEach(i -> { - assertThat(childProblems.get(i).getSequence()).isEqualTo(i); - }); - - // ✅ 개별 자식 문제 검증 - assertThat(childProblems.get(0).getImageUrl()).isEqualTo("updatedChild.png"); // 기존 자식 문제 업데이트 확인 - assertThat(childProblems.get(1).getImageUrl()).isEqualTo("newChild.png"); // 새 자식 문제 추가 확인 + @Nested + class 문제_업데이트_정상_동작 { + + @Test + void 문제_업데이트_성공() { + // when + ProblemGetResponse response = problemUpdateService.updateProblem(1L, + problemUpdateRequest); + + // then + assertThat(response).isNotNull(); + assertThat(response.problemId()).startsWith("22230310"); // 문제 ID 확인 + assertThat(response.problemType()).isEqualTo(ProblemType.VARIANT_PROBLEM); + assertThat(response.practiceTestId()).isEqualTo(2L); + assertThat(response.number()).isEqualTo(10); + assertThat(response.conceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(1L, 2L, 3L)); + assertThat(response.answer()).isEqualTo("정답"); + assertThat(response.title()).isEqualTo("업데이트된 제목"); + assertThat(response.difficulty()).isEqualTo(3); + assertThat(response.memo()).isEqualTo("업데이트된 메모"); + + // 이미지 URL 검증 + assertThat(response.mainProblemImageUrl()).isEqualTo("updatedMainProblem.png"); + assertThat(response.mainAnalysisImageUrl()).isEqualTo("updatedMainAnalysis.png"); + assertThat(response.readingTipImageUrl()).isEqualTo("updatedReadingTip.png"); + assertThat(response.seniorTipImageUrl()).isEqualTo("updatedSeniorTip.png"); + assertThat(response.mainHandwritingExplanationImageUrl()) + .isEqualTo("updatedMainHandwriting.png"); + assertThat(response.prescriptionImageUrls()) + .containsExactly("prescription1.png", "prescription2.png"); + + // 답안 유형 검증 + assertThat(response.answerType()).isEqualTo(AnswerType.SHORT_STRING_ANSWER); + + Problem updatedProblem = problemRepository.findByIdElseThrow(1L); + + // 자식 문제 검증 + List<ChildProblem> childProblems = updatedProblem.getChildProblems(); + assertThat(childProblems).hasSize(2); // 기존 2개 → 1개 삭제, 1개 추가 후 2개 + + // 첫 번째 자식 문제 검증 (업데이트된 기존 문제) + ChildProblem updatedChild = childProblems.get(0); + assertThat(updatedChild.getId()).isEqualTo(1L); + assertThat(updatedChild.getImageUrl()).isEqualTo("updatedChild.png"); + assertThat(updatedChild.getAnswerType()).isEqualTo(AnswerType.MULTIPLE_CHOICE); + assertThat(updatedChild.getAnswer()).isEqualTo("2"); + assertThat(updatedChild.getConceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(2L, 3L)); + assertThat(updatedChild.getSequence()).isEqualTo(0); + + // 두 번째 자식 문제 검증 (새로 추가된 문제) + ChildProblem newChild = childProblems.get(1); + assertThat(newChild.getImageUrl()).isEqualTo("newChild.png"); + assertThat(newChild.getAnswerType()).isEqualTo(AnswerType.SHORT_STRING_ANSWER); + assertThat(newChild.getAnswer()).isEqualTo("새로운 정답"); + assertThat(newChild.getConceptTagIds()).containsExactlyInAnyOrderElementsOf(Set.of(1L, 2L)); + assertThat(newChild.getSequence()).isEqualTo(1); + + // 부모 문제의 conceptTagIds가 자식 문제의 conceptTagIds를 모두 포함하는지 검증 + Set<Long> problemTags = updatedProblem.getConceptTagIds(); + childProblems.forEach(child -> { + assertThat(problemTags).containsAll(child.getConceptTagIds()); + }); + + // 자식 문제 순서가 올바르게 정렬되었는지 확인 + IntStream.range(0, childProblems.size()).forEach(i -> { + assertThat(childProblems.get(i).getSequence()).isEqualTo(i); + }); + } } -} \ No newline at end of file + + @Nested + class 문제_업데이트_예외_처리 { + + @Test + void 문제_업데이트_실패_존재하지_않는_ID() { + // given + String invalidId = "999999999999"; // 존재하지 않는 문제 ID + + // when & then + assertThatThrownBy(() -> problemUpdateService.updateProblem(9999L, problemUpdateRequest)) + .isInstanceOf(NotFoundException.class); + } + + @Test + void 문제_업데이트_실패_잘못된_ConceptTag() { + // given + ProblemUpdateRequest invalidRequest = new ProblemUpdateRequest( + ProblemType.GICHUL_PROBLEM, + 1L, + 20, + Set.of(999L, 1000L), + "정답", + "잘못된 제목", + 3, + "잘못된 메모", + "updatedMainProblem.png", + "updatedMainAnalysis.png", + "updatedMainHandwriting.png", // 추가 + "updatedReadingTip.png", + "updatedSeniorTip.png", + List.of("prescription1.png"), // List<String>으로 변경 + AnswerType.SHORT_STRING_ANSWER, + List.of(), + List.of() + ); + + // when & then + assertThatThrownBy(() -> problemUpdateService.updateProblem(9999L, invalidRequest)) + .isInstanceOf(NotFoundException.class); + } + } +} diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java index d682201..539d0a0 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/ProblemSetServiceTest.java @@ -44,7 +44,7 @@ void setUp() { // 초기 문항 세트 생성 요청 데이터 준비 problemSetPostRequest = new ProblemSetPostRequest( "초기 문항세트", - List.of("24052001001", "24052001002", "24052001003") + List.of(1L, 2L, 3L) ); } @@ -60,9 +60,6 @@ void setUp() { assertThat(savedProblemSet).isNotNull(); assertThat(savedProblemSet.getTitle().getValue()).isEqualTo("초기 문항세트"); assertThat(savedProblemSet.getProblemIds()).hasSize(3); - assertThat(savedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001001"); - assertThat(savedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001002"); - assertThat(savedProblemSet.getProblemIds().get(2).getId()).isEqualTo("24052001003"); } @Test @@ -72,7 +69,7 @@ void setUp() { // when ProblemReorderRequest reorderRequest = new ProblemReorderRequest( - List.of("24052001003", "24052001001", "24052001002") + List.of(1L, 2L, 3L) ); problemSetUpdateService.reorderProblems(problemSetId, reorderRequest); @@ -80,9 +77,6 @@ void setUp() { ProblemSet updatedProblemSet = problemSetRepository.findById(problemSetId) .orElseThrow(() -> new IllegalArgumentException("문항세트를 찾을 수 없습니다.")); - assertThat(updatedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001003"); - assertThat(updatedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001001"); - assertThat(updatedProblemSet.getProblemIds().get(2).getId()).isEqualTo("24052001002"); } @Test @@ -93,7 +87,7 @@ void setUp() { // when ProblemSetUpdateRequest updateRequest = new ProblemSetUpdateRequest( "업데이트된 문항세트", - List.of("24052001002", "24052001003") + List.of(1L, 2L) ); problemSetUpdateService.updateProblemSet(problemSetId, updateRequest); @@ -102,8 +96,7 @@ void setUp() { assertThat(updatedProblemSet.getTitle().getValue()).isEqualTo("업데이트된 문항세트"); assertThat(updatedProblemSet.getProblemIds()).hasSize(2); - assertThat(updatedProblemSet.getProblemIds().get(0).getId()).isEqualTo("24052001002"); - assertThat(updatedProblemSet.getProblemIds().get(1).getId()).isEqualTo("24052001003"); + } @Test @@ -112,8 +105,10 @@ void setUp() { Long problemSetId = problemSetSaveService.createProblemSet(problemSetPostRequest); // when - ProblemSetConfirmStatus firstToggleStatus = problemSetUpdateService.toggleConfirmProblemSet(problemSetId); // CONFIRMED - ProblemSetConfirmStatus secondToggleStatus = problemSetUpdateService.toggleConfirmProblemSet(problemSetId); // NOT_CONFIRMED + ProblemSetConfirmStatus firstToggleStatus = problemSetUpdateService.toggleConfirmProblemSet( + problemSetId); // CONFIRMED + ProblemSetConfirmStatus secondToggleStatus = problemSetUpdateService.toggleConfirmProblemSet( + problemSetId); // NOT_CONFIRMED // then assertThat(firstToggleStatus).isEqualTo(ProblemSetConfirmStatus.CONFIRMED); // 첫 번째 호출 후 컨펌 상태 확인 @@ -128,7 +123,7 @@ void setUp() { // 유효하지 않은 문항을 포함하도록 설정 (문항 ID가 존재하지 않거나 필수 필드가 누락된 경우) ProblemSetUpdateRequest invalidUpdateRequest = new ProblemSetUpdateRequest( "유효하지 않은 문항세트", - List.of("24052001001", "24052001004") + List.of(1L, 4L) ); problemSetUpdateService.updateProblemSet(problemSetId, invalidUpdateRequest); @@ -158,12 +153,12 @@ void setUp() { // given ProblemSetPostRequest emptyTitleRequest = new ProblemSetPostRequest( "", // 빈 문자열 제목 - List.of("24052001001", "24052001002", "24052001003") + List.of(1L, 2L, 3L) ); ProblemSetPostRequest nullTitleRequest = new ProblemSetPostRequest( null, // null 제목 - List.of("24052001001", "24052001002", "24052001003") + List.of(1L, 2L, 3L) ); // when diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java index 5085caf..4a34089 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/repository/ProblemSetSearchRepositoryCustomTest.java @@ -46,7 +46,7 @@ public class ProblemSetSearchRepositoryCustomTest { @Test void 문항타이틀_포함_검색() { // when - List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, "설명 1", null); + List<ProblemSetSearchGetResponse> result = problemSetSearchRepository.search(null, "설명", null); // then assertThat(result).hasSize(2); diff --git a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java index 85e7e40..20dc272 100644 --- a/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java +++ b/src/test/java/com/moplus/moplus_server/domain/problemset/service/ProblemSetGetServiceTest.java @@ -3,9 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import com.moplus.moplus_server.domain.problem.domain.problem.Problem; -import com.moplus.moplus_server.domain.problem.domain.problem.ProblemId; -import com.moplus.moplus_server.domain.problem.dto.request.ProblemPostRequest; +import com.moplus.moplus_server.domain.problem.domain.practiceTest.PracticeTestTag; import com.moplus.moplus_server.domain.problem.repository.PracticeTestTagRepository; import com.moplus.moplus_server.domain.problem.repository.ProblemRepository; import com.moplus.moplus_server.domain.problemset.domain.ProblemSet; @@ -13,7 +11,6 @@ import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository; import com.moplus.moplus_server.global.error.exception.NotFoundException; import java.util.List; -import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -24,7 +21,7 @@ @Transactional @ActiveProfiles("h2test") -@Sql({"/practice-test-tag.sql", "/concept-tag.sql"}) +@Sql({"/practice-test-tag.sql", "/concept-tag.sql", "/insert-problem.sql"}) @SpringBootTest public class ProblemSetGetServiceTest { @@ -41,47 +38,13 @@ public class ProblemSetGetServiceTest { private PracticeTestTagRepository practiceTestTagRepository; private ProblemSet savedProblemSet; - private Problem savedProblem; @BeforeEach void setUp() { - // 문제 생성 요청 데이터 준비 - ProblemPostRequest problemPostRequest = new ProblemPostRequest( - Set.of(1L, 2L), - 1L, - 1, - "1", - "문제 설명", - "mainProblem.png", - "mainAnalysis.png", - "readingTip.png", - "seniorTip.png", - "prescription.png", - List.of() - ); - - // 문제 저장 - ProblemId createdProblemId = problemRepository.save( - new Problem( - new ProblemId("24052001001"), - practiceTestTagRepository.findByIdElseThrow(1L), - 1, - "1", - "문제 설명", - "mainProblem.png", - "mainAnalysis.png", - "readingTip.png", - "seniorTip.png", - "prescription.png", - Set.of(1L, 2L) - ) - ).getId(); - - savedProblem = problemRepository.findByIdElseThrow(createdProblemId); // 문항세트 저장 savedProblemSet = problemSetRepository.save( - new ProblemSet("테스트 문항세트", List.of(savedProblem.getId())) + new ProblemSet("테스트 문항세트", List.of(1L)) ); } @@ -94,7 +57,7 @@ void setUp() { assertThat(response).isNotNull(); assertThat(response.title()).isEqualTo("테스트 문항세트"); assertThat(response.problemSummaries()).hasSize(1); - assertThat(response.problemSummaries().get(0).problemId()).isEqualTo(savedProblem.getId().toString()); + assertThat(response.problemSummaries().get(0).problemId()).isEqualTo("1224052001"); assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); } @@ -109,25 +72,11 @@ void setUp() { @Test void 문항세트_조회_성공_테스트_여러개() { - // given: 여러 개의 문제를 저장 - Problem savedProblem2 = problemRepository.save( - new Problem( - new ProblemId("24052001002"), - practiceTestTagRepository.findByIdElseThrow(1L), - 2, - "2", - "문제 설명2", - "mainProblem2.png", - "mainAnalysis2.png", - "readingTip2.png", - "seniorTip2.png", - "prescription2.png", - Set.of(3L, 4L) - ) - ); + // given + PracticeTestTag practiceTestTag = practiceTestTagRepository.findByIdElseThrow(1L); ProblemSet multipleProblemSet = problemSetRepository.save( - new ProblemSet("여러 문항 테스트 문항세트", List.of(savedProblem.getId(), savedProblem2.getId())) + new ProblemSet("여러 문항 테스트 문항세트", List.of(1L, 2L)) ); // when @@ -139,13 +88,13 @@ void setUp() { assertThat(response.problemSummaries()).hasSize(2); // 첫 번째 문제 검증 - assertThat(response.problemSummaries().get(0).problemId()).isEqualTo(savedProblem.getId().toString()); + assertThat(response.problemSummaries().get(0).problemId()).isEqualTo("1224052001"); assertThat(response.problemSummaries().get(0).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); assertThat(response.problemSummaries().get(0).tagNames()).contains("미분 개념", "적분 개념"); // 두 번째 문제 검증 - assertThat(response.problemSummaries().get(1).problemId()).isEqualTo(savedProblem2.getId().toString()); + assertThat(response.problemSummaries().get(1).problemId()).isEqualTo("1224052002"); assertThat(response.problemSummaries().get(1).practiceTestName()).isEqualTo("2025년 5월 고2 모의고사"); - assertThat(response.problemSummaries().get(1).tagNames()).contains("삼각함수 개념", "행렬 개념"); + assertThat(response.problemSummaries().get(1).tagNames()).contains("미분 개념", "삼각함수 개념"); } } \ No newline at end of file diff --git a/src/test/resources/insert-problem-set.sql b/src/test/resources/insert-problem-set.sql index 051949c..ffca03e 100644 --- a/src/test/resources/insert-problem-set.sql +++ b/src/test/resources/insert-problem-set.sql @@ -1,5 +1,7 @@ -DELETE FROM problem_set_problems; -DELETE FROM problem_set; +DELETE +FROM problem_set_problems; +DELETE +FROM problem_set; -- 문제 세트 추가 INSERT INTO problem_set (problem_set_id, title, is_deleted, confirm_status) @@ -9,8 +11,8 @@ VALUES (2, '2025년 5월 고3 모의고사 문제 세트', false, 'CONFIRMED'); -- 문제 세트에 포함된 문제 추가 INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) -VALUES (1, '240520012001', 0), - (1, '240520012002', 1); +VALUES (1, 1, 0), + (1, 2, 1); INSERT INTO problem_set_problems (problem_set_id, problem_id, sequence) -VALUES (2, '240520012001', 0), - (2, '240520012002', 1); \ No newline at end of file +VALUES (2, 1, 0), + (2, 2, 1); \ No newline at end of file diff --git a/src/test/resources/insert-problem.sql b/src/test/resources/insert-problem.sql index d9ecdfb..add8767 100644 --- a/src/test/resources/insert-problem.sql +++ b/src/test/resources/insert-problem.sql @@ -1,32 +1,47 @@ -DELETE -FROM child_problem_concept; -DELETE -FROM child_problem; +INSERT INTO problem (problem_id, + problem_admin_id, + practice_test_id, + number, + problem_type, + title, + answer, + difficulty, + memo, + main_problem_image_url, + main_analysis_image_url, + reading_tip_image_url, + senior_tip_image_url, + prescription_image_urls, + answer_type, + is_confirmed) +VALUES (1, '1224052001', 1, 1, 'GICHUL_PROBLEM', '제목1', '1', 5, '기존 문제 설명 1', + 'mainProblem.png1', 'mainAnalysis.png1', 'readingTip.png1', 'seniorTip.png1', + 'prescription.png1', 'MULTIPLE_CHOICE', false), + (2, '1224052002', 1, 1, 'GICHUL_PROBLEM', '제목2', '1', 5, '기존 문제 설명 2', + 'mainProblem.png2', 'mainAnalysis.png2', 'readingTip.png2', 'seniorTip.png2', + 'prescription.png2', 'MULTIPLE_CHOICE', false); -INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, - main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, - is_published, is_variation) -VALUES ('240520012001', 1, 1, '1', '기존 문제 설명 1', - 'mainProblem.png1', 'mainAnalysis.png1', 'readingTip.png1', 'seniorTip.png1', 'prescription.png1', - false, false), - ('240520012002', 1, 1, '1', '기존 문제 설명 2', - 'mainProblem.png2', 'mainAnalysis.png2', 'readingTip.png2', 'seniorTip.png2', 'prescription.png2', - false, false); --- 기존 자식 문제(ChildProblem) 삽입 -INSERT INTO child_problem (child_problem_id, problem_id, image_url, problem_type, answer, sequence) -VALUES (1, '240520012001', 'child1.png', 'MULTIPLE_CHOICE', '1', 0), - (2, '240520012001', 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 0); +INSERT INTO child_problem (child_problem_id, + problem_id, -- Long 타입으로 변경 (부모 Problem의 id 참조) + image_url, + answer_type, + answer, + sequence) +VALUES (1, 1, 'child1.png', 'MULTIPLE_CHOICE', '1', 0), + (2, 1, 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 0); -- 문제-컨셉 태그 연결 (기존 문제의 ConceptTag) -INSERT INTO problem_concept (problem_id, concept_tag_id) -VALUES ('240520012001', 1), - ('240520012001', 2), - ('240520012001', 3), - ('240520012002', 1), - ('240520012002', 3); +INSERT INTO problem_concept (problem_id, -- Long 타입으로 변경 (Problem의 id 참조) + concept_tag_id) +VALUES (1, 1), + (1, 2), + (1, 3), + (2, 1), + (2, 3); -- 자식 문제-컨셉 태그 연결 -INSERT INTO child_problem_concept (child_problem_id, concept_tag_id) +INSERT INTO child_problem_concept (child_problem_id, + concept_tag_id) VALUES (1, 3), (1, 4), (2, 5), diff --git a/src/test/resources/insert-problem2.sql b/src/test/resources/insert-problem2.sql index 6e50eca..b999009 100644 --- a/src/test/resources/insert-problem2.sql +++ b/src/test/resources/insert-problem2.sql @@ -1,30 +1,83 @@ -DELETE FROM problem_concept; -DELETE -FROM child_problem_concept; -DELETE -FROM child_problem; -DELETE FROM problem_set_problems; -DELETE FROM problem_set; -DELETE FROM problem; - -- problem 데이터 삽입 -INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, - main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, - is_published, is_variation) -VALUES ('24052001001', 1, 1, '1', '기존 문제 설명', - 'mainProblem.png', 'mainAnalysis.png', 'readingTip.png', 'seniorTip.png', 'prescription.png', - false, false), - ('24052001002', 1, 2, '2', '문제 2 설명', - 'mainProblem2.png', 'mainAnalysis2.png', 'readingTip2.png', 'seniorTip2.png', 'prescription2.png', - false, false), - ('24052001003', 1, 3, '3', '문제 3 설명', - 'mainProblem3.png', 'mainAnalysis3.png', 'readingTip3.png', 'seniorTip3.png', 'prescription3.png', - false, false); +INSERT INTO problem (problem_id, + problem_admin_id, + practice_test_id, + number, + problem_type, + title, + answer, + difficulty, + memo, + main_problem_image_url, + main_analysis_image_url, + main_handwriting_explanation_image_url, + reading_tip_image_url, + senior_tip_image_url, + prescription_image_urls, + answer_type, + is_confirmed) +VALUES (1, '24052001001', 1, 1, 'GICHUL_PROBLEM', '제목1', '1', 5, '기존 문제 설명', + 'mainProblem.png', 'mainAnalysis.png', 'mainHandwriting1.png', 'readingTip.png', 'seniorTip.png', + 'prescription1.png, prescription2.png', 'MULTIPLE_CHOICE', false), + + (2, '24052001002', 1, 2, 'GICHUL_PROBLEM', '제목2', '2', 4, '문제 2 설명', + 'mainProblem2.png', 'mainAnalysis2.png', 'mainHandwriting2.png', 'readingTip2.png', 'seniorTip2.png', + 'prescription3.png, prescription4.png', 'MULTIPLE_CHOICE', false), + + (3, '24052001003', 1, 3, 'GICHUL_PROBLEM', '제목3', '3', 3, '문제 3 설명', + 'mainProblem3.png', 'mainAnalysis3.png', 'mainHandwriting3.png', 'readingTip3.png', 'seniorTip3.png', + 'prescription5.png, prescription6.png', 'SHORT_STRING_ANSWER', true); + +-- 자식 문제 데이터 삽입 +INSERT INTO child_problem (child_problem_id, + problem_id, + image_url, + answer_type, + answer, + sequence) +VALUES (1, 1, 'child1.png', 'MULTIPLE_CHOICE', '1', 0), + (2, 1, 'child2.png', 'SHORT_STRING_ANSWER', '정답2', 1), + (3, 2, 'child3.png', 'MULTIPLE_CHOICE', '2', 0), + (4, 3, 'child4.png', 'SHORT_STRING_ANSWER', '3', 0); + +-- 문제-컨셉 태그 연결 +INSERT INTO problem_concept (problem_id, concept_tag_id) +VALUES (1, 1), + (1, 2), + (2, 2), + (2, 3), + (3, 3), + (3, 4); + +-- 자식 문제-컨셉 태그 연결 +INSERT INTO child_problem_concept (child_problem_id, concept_tag_id) +VALUES (1, 1), + (1, 2), + (2, 2), + (2, 3), + (3, 3), + (3, 4), + (4, 1), + (4, 4); --- 유효하지 않은 문제 데이터 삽입 (answer와 mainProblemImageUrl이 NULL) -INSERT INTO problem (problem_id, practice_test_id, number, answer, comment, main_problem_image_url, - main_analysis_image_url, reading_tip_image_url, senior_tip_image_url, prescription_image_url, - is_published, is_variation) -VALUES ('24052001004', 1, 4, '', '유효하지 않은 문제 설명', - '', 'mainAnalysis4.png', 'readingTip4.png', 'seniorTip4.png', 'prescription4.png', - false, false); \ No newline at end of file +-- 유효하지 않은 문제 데이터 삽입 +INSERT INTO problem (problem_id, + problem_admin_id, + practice_test_id, + number, + problem_type, + title, + answer, + difficulty, + memo, + main_problem_image_url, + main_analysis_image_url, + main_handwriting_explanation_image_url, + reading_tip_image_url, + senior_tip_image_url, + prescription_image_urls, + answer_type, + is_confirmed) +VALUES (4, '24052001004', 1, 4, 'GICHUL_PROBLEM', '', '', 1, '유효하지 않은 문제 설명', + '', 'mainAnalysis4.png', '', 'readingTip4.png', 'seniorTip4.png', + '', 'MULTIPLE_CHOICE', false); \ No newline at end of file diff --git a/src/test/resources/practice-test-tag.sql b/src/test/resources/practice-test-tag.sql index 58625ce..b1f5b17 100644 --- a/src/test/resources/practice-test-tag.sql +++ b/src/test/resources/practice-test-tag.sql @@ -1,2 +1,3 @@ INSERT INTO practice_test_tag (practice_test_tag_id, name, test_year, test_month, subject, area) -VALUES (1, '2025년 5월 고2 모의고사', 2024, 5, '고2', '수학'); \ No newline at end of file +VALUES (1, '2025년 5월 고2 모의고사', 2024, 5, '고2', '수학'), + (2, '2023년 3월 고2 모의고사', 2023, 3, '고2', '수학'); \ No newline at end of file