Skip to content
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
.env
local.env

### STS ###
.apt_generated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,13 @@ default void existsByIdElseThrow(Set<Long> ids) {
throw new NotFoundException(ErrorCode.CONCEPT_TAG_NOT_FOUND_IN_LIST);
}
}


default List<ConceptTag> findAllByIdsElseThrow(Set<Long> ids) {
List<ConceptTag> conceptTags = findAllById(ids);
if (conceptTags.size() != ids.size()) {
throw new NotFoundException(ErrorCode.CONCEPT_TAG_NOT_FOUND_IN_LIST);
}
return conceptTags;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@
import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest;
import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetPostRequest;
import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest;
import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetGetResponse;
import com.moplus.moplus_server.domain.problemset.service.ProblemSetDeleteService;
import com.moplus.moplus_server.domain.problemset.service.ProblemSetGetService;
import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService;
import com.moplus.moplus_server.domain.problemset.service.ProblemSetUpdateService;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
Expand All @@ -26,6 +30,7 @@ public class ProblemSetController {
private final ProblemSetSaveService problemSetSaveService;
private final ProblemSetUpdateService problemSetUpdateService;
private final ProblemSetDeleteService problemSetDeleteService;
private final ProblemSetGetService problemSetGetService;

@PostMapping("")
@Operation(summary = "문항세트 생성", description = "문항세트를 생성합니다. 문항은 요청 순서대로 저장합니다.")
Expand All @@ -35,6 +40,7 @@ public ResponseEntity<Long> createProblemSet(
return ResponseEntity.ok(problemSetSaveService.createProblemSet(request));
}

@Hidden
@PutMapping("/{problemSetId}/sequence")
@Operation(summary = "세트 문항순서 변경", description = "문항세트 내의 문항 리스트의 순서를 변경합니다.")
public ResponseEntity<Void> reorderProblems(
Expand Down Expand Up @@ -65,7 +71,17 @@ public ResponseEntity<Void> deleteProblemSet(

@PutMapping("/{problemSetId}/confirm")
@Operation(summary = "문항세트 컨펌 토글", description = "문항세트의 컨펌 상태를 토글합니다.")
public ResponseEntity<ProblemSetConfirmStatus> toggleConfirmProblemSet(@PathVariable Long problemSetId) {
public ResponseEntity<ProblemSetConfirmStatus> toggleConfirmProblemSet(
@PathVariable Long problemSetId
) {
return ResponseEntity.ok(problemSetUpdateService.toggleConfirmProblemSet(problemSetId));
}
}

@GetMapping("/{problemSetId}")
@Operation(summary = "문항세트 개별 조회", description = "문항세트를 조회합니다.")
public ResponseEntity<ProblemSetGetResponse> getProblemSet(
@PathVariable Long problemSetId
) {
return ResponseEntity.ok(problemSetGetService.getProblemSet(problemSetId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.moplus.moplus_server.domain.problemset.controller;


import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse;
import com.moplus.moplus_server.domain.problemset.repository.ProblemSetSearchRepositoryCustom;
import io.swagger.v3.oas.annotations.Operation;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/problemSet")
@RequiredArgsConstructor
public class ProblemSetSearchController {

private final ProblemSetSearchRepositoryCustom problemSetSearchRepository;

@GetMapping("/search")
@Operation(
summary = "문항세트 검색",
description = "문항세트 타이틀, 문항세트 내 포함된 개념태그, 문항세트 내 포함된 문항 타이틀로 검색합니다."
)
public ResponseEntity<List<ProblemSetSearchGetResponse>> search(
@RequestParam(value = "problemSetTitle", required = false) String problemSetTitle,
@RequestParam(value = "problemTitle", required = false) String problemTitle,
@RequestParam(value = "conceptTagNames", required = false) List<String> conceptTagNames
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image 문항 메모가 들어가야될 것 같아요

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문항 변동 사항 반영 전이라 아직 problem에 메모가 없는데 이후에 수정이 나을까요?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment가 memo로 바뀌는걸로 알고 있습니다

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문항 title외에도 문항memo 필터링 요소가 추가되는 것일까요?
만약 그렇다면, 추가된 이후에 수정하겠습니다!

) {
List<ProblemSetSearchGetResponse> problemSets = problemSetSearchRepository.search(problemSetTitle, problemTitle, conceptTagNames);
return ResponseEntity.ok(problemSets);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
Expand All @@ -33,7 +34,8 @@ public class ProblemSet extends BaseEntity {
@Column(name = "problem_set_id")
Long id;

private String name;
@Embedded
private Title title;
private boolean isDeleted;

@Enumerated(EnumType.STRING)
Expand All @@ -46,8 +48,8 @@ public class ProblemSet extends BaseEntity {
private List<ProblemId> problemIds = new ArrayList<>();

@Builder
public ProblemSet(String name, List<ProblemId> problemIds) {
this.name = name;
public ProblemSet(String title, List<ProblemId> problemIds) {
this.title = new Title(title);
this.isDeleted = false;
this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED;
this.problemIds = problemIds;
Expand All @@ -73,8 +75,8 @@ public void toggleConfirm(List<Problem> problems) {
this.confirmStatus = this.confirmStatus.toggle();
}

public void updateProblemSet(String name, List<ProblemId> newProblems) {
this.name = name;
public void updateProblemSet(String title, List<ProblemId> newProblems) {
this.title = new Title(title);
this.problemIds = newProblems;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.moplus.moplus_server.domain.problemset.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Embeddable
@NoArgsConstructor
public class Title {

private static final String DEFAULT_TITLE = "제목 없음";

@Column(name = "title", nullable = false)
private String value;

public Title(String title) {
this.value = verifyTitle(title);
}

private static String verifyTitle(String title) {
return (title == null || title.trim().isEmpty()) ? DEFAULT_TITLE : title;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import java.util.List;

public record ProblemSetPostRequest(
String problemSetName,
String problemSetTitle,
List<String> problems
) {
public ProblemSet toEntity(List<ProblemId> problemIdList) {
return ProblemSet.builder()
.name(this.problemSetName)
.title(this.problemSetTitle)
.problemIds(problemIdList)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import java.util.List;

public record ProblemSetUpdateRequest(
String problemSetName,
String problemSetTitle,
List<String> problems
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.moplus.moplus_server.domain.problemset.dto.response;

import com.moplus.moplus_server.domain.problemset.domain.ProblemSet;
import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus;
import java.util.List;
import lombok.Builder;

@Builder
public record ProblemSetGetResponse(
Long id,
String title,
ProblemSetConfirmStatus confirmStatus,
List<ProblemSummaryResponse> problemSummaries
) {
public static ProblemSetGetResponse of(ProblemSet problemSet, List<ProblemSummaryResponse> problemSummaries) {

return ProblemSetGetResponse.builder()
.id(problemSet.getId())
.title(problemSet.getTitle().getValue())
.confirmStatus(problemSet.getConfirmStatus())
.problemSummaries(problemSummaries)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.moplus.moplus_server.domain.problemset.dto.response;

import java.util.List;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ProblemSetSearchGetResponse {
private String problemSetTitle;
private List<ProblemThumbnailResponse> problemThumbnailResponses;

public ProblemSetSearchGetResponse(
String problemSetTitle, List<ProblemThumbnailResponse> problemThumbnailResponses
) {
this.problemSetTitle = problemSetTitle;
this.problemThumbnailResponses = problemThumbnailResponses;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.moplus.moplus_server.domain.problemset.dto.response;

import com.moplus.moplus_server.domain.problem.domain.problem.Problem;
import java.util.List;
import lombok.Builder;

@Builder
public record ProblemSummaryResponse(
String problemId,
int number,
String practiceTestName,
String comment,
String mainProblemImageUrl,
List<String> tagNames
) {
public static ProblemSummaryResponse of(Problem problem, String practiceTestName, List<String> tagNames) {

return ProblemSummaryResponse.builder()
.problemId(problem.getId().toString())
.number(problem.getNumber())
.comment(problem.getComment())
.mainProblemImageUrl(problem.getMainProblemImageUrl())
.practiceTestName(practiceTestName)
.tagNames(tagNames)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.moplus.moplus_server.domain.problemset.dto.response;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ProblemThumbnailResponse {
private String mainProblemImageUrl;

public ProblemThumbnailResponse(String mainProblemImageUrl) {
this.mainProblemImageUrl = mainProblemImageUrl;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.moplus.moplus_server.domain.problemset.repository;

import static com.moplus.moplus_server.domain.concept.domain.QConceptTag.conceptTag;
import static com.moplus.moplus_server.domain.problem.domain.problem.QProblem.problem;
import static com.moplus.moplus_server.domain.problemset.domain.QProblemSet.problemSet;

import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetSearchGetResponse;
import com.moplus.moplus_server.domain.problemset.dto.response.ProblemThumbnailResponse;
import com.querydsl.core.group.GroupBy;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class ProblemSetSearchRepositoryCustom {

private final JPAQueryFactory queryFactory;

public List<ProblemSetSearchGetResponse> search(String problemSetTitle, String problemTitle, List<String> conceptTagNames) {
return queryFactory
.from(problemSet)
.leftJoin(problem).on(problem.id.in(problemSet.problemIds)) // 문제 세트 내 포함된 문항과 조인
.leftJoin(conceptTag).on(conceptTag.id.in(problem.conceptTagIds)) // 문제의 개념 태그 조인
.where(
containsProblemSetTitle(problemSetTitle),
containsProblemTitle(problemTitle),
containsConceptTagNames(conceptTagNames)
)
.distinct()
.transform(GroupBy.groupBy(problemSet.id).list(
Projections.constructor(ProblemSetSearchGetResponse.class,
problemSet.title.value,
GroupBy.list(
Projections.constructor(ProblemThumbnailResponse.class,
problem.mainProblemImageUrl
)
)
)
));
}

private BooleanExpression containsProblemSetTitle(String 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);
}

private BooleanExpression containsConceptTagNames(List<String> conceptTagNames) {
return (conceptTagNames == null || conceptTagNames.isEmpty()) ? null : conceptTag.name.in(conceptTagNames);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.moplus.moplus_server.domain.problemset.service;

import com.moplus.moplus_server.domain.concept.domain.ConceptTag;
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;
import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSetGetResponse;
import com.moplus.moplus_server.domain.problemset.dto.response.ProblemSummaryResponse;
import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ProblemSetGetService {

private final ProblemSetRepository problemSetRepository;
private final ProblemRepository problemRepository;
private final PracticeTestTagRepository practiceTestTagRepository;
private final ConceptTagRepository conceptTagRepository;

@Transactional(readOnly = true)
public ProblemSetGetResponse getProblemSet(Long problemSetId) {

ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId);

List<ProblemSummaryResponse> problemSummaries = new ArrayList<>();
for (ProblemId problemId : problemSet.getProblemIds()) {
Problem problem = problemRepository.findByIdElseThrow(problemId);
PracticeTestTag practiceTestTag = practiceTestTagRepository.findByIdElseThrow(problem.getPracticeTestId());
List<String> tagNames = conceptTagRepository.findAllByIdsElseThrow(problem.getConceptTagIds())
.stream()
.map(ConceptTag::getName)
.toList();
problemSummaries.add(ProblemSummaryResponse.of(problem, practiceTestTag.getName(), tagNames));
}
return ProblemSetGetResponse.of(problemSet, problemSummaries);
}
}
Loading