Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
000f92a
[feat/#29] 문항 세트 루트 엔티티 구현
seokbeom00 Jan 31, 2025
b75932c
[feat/#29] 발행 루트 엔티티 구현
seokbeom00 Jan 31, 2025
6c7a704
[fix/#29] ProblemId 리스트로 변경
seokbeom00 Jan 31, 2025
8795115
[feat/#31] 문항 순서 보장 로직 추가
seokbeom00 Jan 31, 2025
4295340
[feat/#31] 문항세트 생성 API 구현
seokbeom00 Jan 31, 2025
979177b
[feat/#31] 문항세트 문항 순서 변경 API 구현
seokbeom00 Jan 31, 2025
0736230
[feat/#31] 문항세트 수정, 삭제, 컨펌 API 구현
seokbeom00 Jan 31, 2025
182e355
[feat/#31] 문항세트 서비스 분리
seokbeom00 Feb 1, 2025
154e973
[feat/#31] 문항세트 컨펌 시 문항 유효성 검사
seokbeom00 Feb 2, 2025
48d1cb6
[fix/#31] 문항세트 생성 로직 수정
seokbeom00 Feb 2, 2025
cfb4508
[feat/#31] 문항세트 컨펌 시 컨펌상태 반환
seokbeom00 Feb 2, 2025
16fd5d5
[fix/#31] 문항세트 업데이트 방식 변경
seokbeom00 Feb 2, 2025
a041472
[test/#31] 문항 세트 서비스 테스트 코드 작성
seokbeom00 Feb 2, 2025
fbf5520
[fix/#31] 테스트 문항 데이터 null값 삭제
seokbeom00 Feb 2, 2025
ffa7cf3
[fix/#31] 문항세트 테스트 시 기존 데이터 삭제
seokbeom00 Feb 2, 2025
d07fbe4
[feat/#31] 문항세트 컨펌 상태로 바꾸는 경우만 문항 유효성 검사
seokbeom00 Feb 2, 2025
b604b25
[fix/#31] 문항세트 생성 후 롤백
seokbeom00 Feb 2, 2025
c67c13f
[fix/#31] 테스트 시 연관 테이블 삭제
seokbeom00 Feb 2, 2025
dc6ee2a
[feat/#31] 문항 토글 로직 수정
seokbeom00 Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,15 @@ public void updateChildProblem(List<ChildProblem> inputChildProblems) {
public void deleteChildProblem(List<Long> deleteChildProblems) {
childProblems.removeIf(childProblem -> deleteChildProblems.contains(childProblem.getId()));
}
}

public boolean isValid() {
return answer != null && !answer.getValue().isEmpty()
&& practiceTestId != null
&& comment != null && !comment.isEmpty()
&& readingTipImageUrl != null && !readingTipImageUrl.isEmpty()
&& seniorTipImageUrl != null && !seniorTipImageUrl.isEmpty()
&& prescriptionImageUrl != null && !prescriptionImageUrl.isEmpty()
&& mainProblemImageUrl != null && !mainProblemImageUrl.isEmpty()
&& mainAnalysisImageUrl != null && !mainAnalysisImageUrl.isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.moplus.moplus_server.domain.problemset.controller;

import com.moplus.moplus_server.domain.problemset.domain.ProblemSetConfirmStatus;
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.service.ProblemSetDeleteService;
import com.moplus.moplus_server.domain.problemset.service.ProblemSetSaveService;
import com.moplus.moplus_server.domain.problemset.service.ProblemSetUpdateService;
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.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

private final ProblemSetSaveService problemSetSaveService;
private final ProblemSetUpdateService problemSetUpdateService;
private final ProblemSetDeleteService problemSetDeleteService;

@PostMapping("")
@Operation(summary = "문항세트 생성", description = "문항세트를 생성합니다. 문항은 요청 순서대로 저장합니다.")
public ResponseEntity<Long> createProblemSet(
@RequestBody ProblemSetPostRequest request
) {
return ResponseEntity.ok(problemSetSaveService.createProblemSet(request));
}

@PutMapping("/{problemSetId}/sequence")
@Operation(summary = "세트 문항순서 변경", description = "문항세트 내의 문항 리스트의 순서를 변경합니다.")
public ResponseEntity<Void> reorderProblems(
@PathVariable Long problemSetId,
@RequestBody ProblemReorderRequest request) {
problemSetUpdateService.reorderProblems(problemSetId, request);
return ResponseEntity.noContent().build();
}

@PutMapping("/{problemSetId}")
@Operation(summary = "문항세트 수정", description = "문항세트의 이름 및 문항 리스트를 수정합니다.")
public ResponseEntity<Void> updateProblemSet(
@PathVariable Long problemSetId,
@RequestBody ProblemSetUpdateRequest request
) {
problemSetUpdateService.updateProblemSet(problemSetId, request);
return ResponseEntity.noContent().build();
}

@DeleteMapping("/{problemSetId}")
@Operation(summary = "문항세트 삭제", description = "문항세트를 삭제합니다. (soft delete)")
public ResponseEntity<Void> deleteProblemSet(
@PathVariable Long problemSetId
) {
problemSetDeleteService.deleteProblemSet(problemSetId);
return ResponseEntity.noContent().build();
}

@PutMapping("/{problemSetId}/confirm")
@Operation(summary = "문항세트 컨펌 토글", description = "문항세트의 컨펌 상태를 토글합니다.")
public ResponseEntity<ProblemSetConfirmStatus> toggleConfirmProblemSet(@PathVariable Long problemSetId) {
return ResponseEntity.ok(problemSetUpdateService.toggleConfirmProblemSet(problemSetId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OrderColumn;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ProblemSet extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "problem_set_id")
Long id;

private String name;
private boolean isDeleted;

@Enumerated(EnumType.STRING)
private ProblemSetConfirmStatus confirmStatus;

@ElementCollection
@CollectionTable(name = "problem_set_problems", joinColumns = @JoinColumn(name = "problem_set_id"))
@Column(name = "problem_id")
@OrderColumn(name = "sequence")
private List<ProblemId> problemIds = new ArrayList<>();

@Builder
public ProblemSet(String name, List<ProblemId> problemIds) {
this.name = name;
this.isDeleted = false;
this.confirmStatus = ProblemSetConfirmStatus.NOT_CONFIRMED;
this.problemIds = problemIds;
}

public void updateProblemOrder(List<ProblemId> newProblems) {
this.problemIds = new ArrayList<>(newProblems);
}

public void deleteProblemSet() {
this.isDeleted = true;
}

public void toggleConfirm(List<Problem> problems) {
if(this.confirmStatus == ProblemSetConfirmStatus.NOT_CONFIRMED){
// 문항 유효성 검사
for (Problem problem : problems) {
if (!problem.isValid()) {
throw new InvalidValueException(ErrorCode.INVALID_CONFIRM_PROBLEM);
}
}
}
this.confirmStatus = this.confirmStatus.toggle();
}

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

public enum ProblemSetConfirmStatus {
CONFIRMED,
NOT_CONFIRMED;
public ProblemSetConfirmStatus toggle() {
return this == CONFIRMED ? NOT_CONFIRMED : CONFIRMED;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.moplus.moplus_server.domain.problemset.dto.request;

import java.util.List;

public record ProblemReorderRequest(
List<String> newProblems
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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 problemSetName,
List<String> problems
) {
public ProblemSet toEntity(List<ProblemId> problemIdList) {
return ProblemSet.builder()
.name(this.problemSetName)
.problemIds(problemIdList)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.moplus.moplus_server.domain.problemset.dto.request;

import java.util.List;

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

import com.moplus.moplus_server.domain.problemset.domain.ProblemSet;
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 ProblemSetRepository extends JpaRepository<ProblemSet, Long> {

default ProblemSet findByIdElseThrow(Long problemSetId) {
return findById(problemSetId).orElseThrow(() -> new NotFoundException(ErrorCode.PROBLEM_SET_NOT_FOUND));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.moplus.moplus_server.domain.problemset.service;

import com.moplus.moplus_server.domain.problemset.domain.ProblemSet;
import com.moplus.moplus_server.domain.problemset.repository.ProblemSetRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ProblemSetDeleteService {

private final ProblemSetRepository problemSetRepository;

@Transactional
public void deleteProblemSet(Long problemSetId) {
ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId);
problemSet.deleteProblemSet();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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.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.repository.ProblemSetRepository;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ProblemSetSaveService {

private final ProblemSetRepository problemSetRepository;
private final ProblemRepository problemRepository;

@Transactional
public Long createProblemSet(ProblemSetPostRequest request) {
// 문제 ID 리스트를 ProblemId 객체로 변환
List<ProblemId> problemIdList = request.problems().stream()
.map(ProblemId::new)
.toList();

// 모든 문항이 DB에 존재하는지 검증
problemIdList.forEach(problemRepository::findByIdElseThrow);

// ProblemSet 생성
ProblemSet problemSet = request.toEntity(problemIdList);

return problemSetRepository.save(problemSet).getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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;
import com.moplus.moplus_server.domain.problemset.dto.request.ProblemReorderRequest;
import com.moplus.moplus_server.domain.problemset.dto.request.ProblemSetUpdateRequest;
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.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ProblemSetUpdateService {

private final ProblemSetRepository problemSetRepository;
private final ProblemRepository problemRepository;

@Transactional
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);
}

@Transactional
public void updateProblemSet(Long problemSetId, ProblemSetUpdateRequest request) {
ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId);

// 문항 리스트 검증
List<ProblemId> problemIdList = request.problems().stream()
.map(ProblemId::new)
.collect(Collectors.toList());
problemIdList.forEach(problemRepository::findByIdElseThrow);

problemSet.updateProblemSet(request.problemSetName(), problemIdList);
}

@Transactional
public ProblemSetConfirmStatus toggleConfirmProblemSet(Long problemSetId) {
ProblemSet problemSet = problemSetRepository.findByIdElseThrow(problemSetId);
List<Problem> problems = new ArrayList<>();
for (ProblemId problemId : problemSet.getProblemIds()) {
Problem problem = problemRepository.findByIdElseThrow(problemId);
problems.add(problem);
}
problemSet.toggleConfirm(problems);
return problemSet.getConfirmStatus();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.moplus.moplus_server.domain.publish.domain;

import com.moplus.moplus_server.global.common.BaseEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.time.LocalDate;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Publish extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "publish_id")
private Long id;

@Column(nullable = false)
private LocalDate publishedDate;

@Column(name = "problem_set_id", nullable = false)
private Long problemSetId;

@Builder
public Publish(LocalDate publishedDate, Long problemSetId) {
this.publishedDate = publishedDate;
this.problemSetId = problemSetId;
}

}
Loading