diff --git a/src/main/java/gymmi/controller/S3Controller.java b/src/main/java/gymmi/controller/S3Controller.java index 0d8b305..af62b25 100644 --- a/src/main/java/gymmi/controller/S3Controller.java +++ b/src/main/java/gymmi/controller/S3Controller.java @@ -23,9 +23,9 @@ public ResponseEntity s3( @GetMapping("/images/workout-proof/presignedUrl/get") public ResponseEntity s3( - @RequestParam("imageUrl") String filenmae + @RequestParam("filename") String filename ) { - String presignedUrl = s3Service.getPresignedUrl(filenmae); + String presignedUrl = s3Service.getPresignedUrl(filename); return ResponseEntity.ok().body(presignedUrl); } diff --git a/src/main/java/gymmi/exceptionhandler/ExceptionController.java b/src/main/java/gymmi/exceptionhandler/ExceptionController.java index 3c2d4ee..069394d 100644 --- a/src/main/java/gymmi/exceptionhandler/ExceptionController.java +++ b/src/main/java/gymmi/exceptionhandler/ExceptionController.java @@ -2,6 +2,7 @@ import gymmi.exceptionhandler.exception.GymmiException; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -12,74 +13,27 @@ public class ExceptionController { @ExceptionHandler - public ResponseEntity handleAll(GymmiException e, HttpServletRequest request) { + public ResponseEntity handleAllCustom(GymmiException e, HttpServletRequest request) { ErrorResponse response = new ErrorResponse(e.getExceptionCode().name(), e.getMessage()); log(e, request.getRequestURI()); return ResponseEntity.status(e.getStatusCode()).body(response); } -// @ExceptionHandler -// public ResponseEntity handle400Exception(BadRequestException e, HttpServletRequest request) { -// ErrorResponse response = new ErrorResponse(e.getErrorTitle(), e.getMessage()); -// log(e, request.getRequestURI()); -// return ResponseEntity.badRequest().body(response); -// } -// -// @ExceptionHandler -// public ResponseEntity handle400Exception(ConstraintViolationException e, HttpServletRequest request) { -// ErrorResponse response = new ErrorResponse("INVALID_INPUT_VALUE", e.getMessage()); -// log(e, request.getRequestURI()); -// return ResponseEntity.badRequest().body(response); -// } -// -// @ExceptionHandler -// public ResponseEntity handle401Exception(AuthenticationException e, HttpServletRequest request) { -// ErrorResponse response = new ErrorResponse(e.getErrorTitle(), e.getMessage()); -// log(e, request.getRequestURI()); -// return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); -// } -// -// @ExceptionHandler -// public ResponseEntity handle403Exception(NotHavePermissionException e, HttpServletRequest request) { -// ErrorResponse response = new ErrorResponse(e.getErrorTitle(), e.getMessage()); -// log(e, request.getRequestURI()); -// return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response); -// } -// -// @ExceptionHandler -// public ResponseEntity handle404Exception(NotFoundException e, HttpServletRequest request) { -// ErrorResponse response = new ErrorResponse(e.getErrorTitle(), e.getMessage()); -// log(e, request.getRequestURI()); -// return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); -// } -// -// @ExceptionHandler -// public ResponseEntity handle500Exception(GymmiException e, HttpServletRequest request) { -// ErrorResponse response = new ErrorResponse(e.getMessage(), e.getMessage()); -// log(e, request.getRequestURI()); -// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); -// } - -// @ExceptionHandler -// public ResponseEntity handle500Exception( -// Exception e, -// HttpServletRequest request, -// HttpServletResponse resp -// ) { -// ErrorResponse response = new ErrorResponse("ERROR", e.getMessage()); -// log(e, request.getRequestURI()); -// return ResponseEntity.status(500).body(response); -// } + @ExceptionHandler + public ResponseEntity handleAll(Exception e, HttpServletRequest request) { + ErrorResponse response = new ErrorResponse("백엔드에게 문의하세요", e.getMessage()); + log(e, request.getRequestURI()); + return ResponseEntity.internalServerError().body(response); + } - private void log(Exception e, String requestURI) { - log.warn(System.lineSeparator() + - "[에러 발생 로그]" + System.lineSeparator() + - "request-url : {}" + System.lineSeparator() + - "error: {}", - requestURI, e.getMessage(), e); + @ExceptionHandler + public ResponseEntity handle400Exception(ConstraintViolationException e, HttpServletRequest request) { + ErrorResponse response = new ErrorResponse("INVALID_INPUT_VALUE", e.getMessage()); + log(e, request.getRequestURI()); + return ResponseEntity.badRequest().body(response); } - private void log1(Exception e, String requestURI) { + private void log(Exception e, String requestURI) { log.warn(System.lineSeparator() + "[에러 발생 로그]" + System.lineSeparator() + "request-url : {}" + System.lineSeparator() + diff --git a/src/main/java/gymmi/exceptionhandler/message/ErrorCode.java b/src/main/java/gymmi/exceptionhandler/message/ErrorCode.java index 2c190e9..9b67edb 100644 --- a/src/main/java/gymmi/exceptionhandler/message/ErrorCode.java +++ b/src/main/java/gymmi/exceptionhandler/message/ErrorCode.java @@ -33,6 +33,11 @@ public enum ErrorCode { INVALID_WORKSPACE_GOAL_SCORE("목표점수는 100점에서 1000점까지 가능합니다.", 400), INVALID_WORKSPACE_MISSION_SCORE("미션 점수는 1~10점까지 가능합니다.", 400), NO_WORKOUT_HISTORY_EXIST_IN_WORKSPACE("해당 워크스페이스의 운동 기록이 아니에요", 403), + NO_OBJECTION_EXIST_IN_WORKSPACE("해당 워크스페이스와 관련 정보가 아니에요", 403), + ALREADY_OBJECTED("이미 이의 신청되었어요.", 400), + NOT_FOUND_OBJECTION("해당 이의 신청이 존재하지 않습니다.", 404), + ALREADY_VOTED("이미 투표하였습니다.", 400), + ALREADY_CLOSED_OBJECTION("이미 종료되었습니다", 400), // user @@ -61,10 +66,7 @@ public enum ErrorCode { UNSUPPORTED_TYPE("지원하지 않는 type 입니다.", 400), NOT_FOUND_WORKER("존재하지 않는 참여자 입니다.", 400), INVALID_DUPLICATION_CHECK_TYPE_VALUE("잘못된 query param value 입니다. : " + DuplicationCheckType.class.getName(), 400), - INVALID_WORKSPACE_STATUS_VALUE("잘못된 query param value 입니다. : " + WorkspaceStatus.class.getName(), 400), - - - ; + INVALID_WORKSPACE_STATUS_VALUE("잘못된 query param value 입니다. : " + WorkspaceStatus.class.getName(), 400); private final String message; private final int statusCode; @@ -81,4 +83,4 @@ public String getMessage() { public int getStatusCode() { return statusCode; } -} + } diff --git a/src/main/java/gymmi/workspace/controller/WorkspaceController.java b/src/main/java/gymmi/workspace/controller/WorkspaceController.java index b732504..a9aea41 100644 --- a/src/main/java/gymmi/workspace/controller/WorkspaceController.java +++ b/src/main/java/gymmi/workspace/controller/WorkspaceController.java @@ -4,38 +4,16 @@ import gymmi.global.Logined; import gymmi.response.IdResponse; import gymmi.workspace.domain.WorkspaceStatus; -import gymmi.workspace.request.CreatingWorkspaceRequest; -import gymmi.workspace.request.EditingIntroductionOfWorkspaceRequest; -import gymmi.workspace.request.JoiningWorkspaceRequest; -import gymmi.workspace.request.MatchingWorkspacePasswordRequest; -import gymmi.workspace.request.WorkingMissionInWorkspaceRequest; -import gymmi.workspace.request.WorkoutRequest; -import gymmi.workspace.response.CheckingCreationOfWorkspaceResponse; -import gymmi.workspace.response.CheckingEntranceOfWorkspaceResponse; -import gymmi.workspace.response.InsideWorkspaceResponse; -import gymmi.workspace.response.JoinedWorkspaceResponse; -import gymmi.workspace.response.MatchingWorkspacePasswordResponse; -import gymmi.workspace.response.MissionResponse; -import gymmi.workspace.response.OpeningTasksBoxResponse; -import gymmi.workspace.response.WorkingScoreResponse; -import gymmi.workspace.response.WorkoutContextResponse; -import gymmi.workspace.response.WorkoutRecordResponse; -import gymmi.workspace.response.WorkspaceIntroductionResponse; -import gymmi.workspace.response.WorkspaceResponse; +import gymmi.workspace.request.*; +import gymmi.workspace.response.*; import gymmi.workspace.service.WorkspaceCommandService; import gymmi.workspace.service.WorkspaceQueryService; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -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.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequiredArgsConstructor @@ -143,7 +121,7 @@ public ResponseEntity> seeMissionsInWorkspace( public ResponseEntity workMissionsInWorkspace( @Logined User user, @PathVariable Long workspaceId, - @RequestBody WorkoutRequest request + @Validated @RequestBody WorkoutRequest request ) { Integer workingScore = workspaceCommandService.workMissionsInWorkspace(user, workspaceId, request); return ResponseEntity.ok().body(new WorkingScoreResponse(workingScore)); @@ -220,13 +198,64 @@ public ResponseEntity toggleRegistrationOfFavoriteMission( } @GetMapping("/workspace/{workspaceId}/missions/favorite") - public ResponseEntity> seeFavoriteMissions( + public ResponseEntity> seeFavoriteMissions( @Logined User user, @PathVariable Long workspaceId ) { - List responses = workspaceQueryService.getFavoriteMissions(user, workspaceId); + List responses = workspaceQueryService.getFavoriteMissions(user, workspaceId); + return ResponseEntity.ok().body(responses); + } + + @GetMapping("/workspaces/{workspaceId}/workout-confirmations") + public ResponseEntity> seeWorkoutConfirmations( + @Logined User user, + @PathVariable Long workspaceId, + @RequestParam int pageNumber + ) { + List responses = workspaceQueryService.getWorkoutConfirmations(user, workspaceId, pageNumber); return ResponseEntity.ok().body(responses); } + @GetMapping("/workspaces/{workspaceId}/workout-confirmations/{workoutConfirmationId}") + public ResponseEntity seeWorkoutConfirmation( + @Logined User user, + @PathVariable Long workspaceId, + @PathVariable Long workoutConfirmationId + ) { + WorkoutConfirmationDetailResponse response = workspaceQueryService.getWorkoutConfirmation(user, workspaceId, workoutConfirmationId); + return ResponseEntity.ok().body(response); + } + + @PostMapping("/workspaces/{workspaceId}/workout-confirmations/{workoutConfirmationId}") + public ResponseEntity objectToWorkoutConfirmation( + @Logined User user, + @PathVariable Long workspaceId, + @PathVariable Long workoutConfirmationId, + @Validated @RequestBody ObjectionRequest request + ) { + workspaceCommandService.objectToWorkoutConfirmation(user, workspaceId, workoutConfirmationId, request); + return ResponseEntity.ok().build(); + } + + @PostMapping("/workspaces/{workspaceId}/objections/{objectionId}") + public ResponseEntity voteToObjection( + @Logined User user, + @PathVariable Long workspaceId, + @PathVariable Long objectionId, + @Validated @RequestBody VoteRequest request + ) { + workspaceCommandService.voteToObjection(user, workspaceId, objectionId, request); + return ResponseEntity.ok().build(); + } + + @GetMapping("/workspaces/{workspaceId}/objections/{objectionId}") + public ResponseEntity seeObjection( + @Logined User user, + @PathVariable Long workspaceId, + @PathVariable Long objectionId + ) { + ObjectionResponse response = workspaceQueryService.getObjection(user, workspaceId, objectionId); + return ResponseEntity.ok().body(response); + } } diff --git a/src/main/java/gymmi/workspace/domain/ObjectionManager.java b/src/main/java/gymmi/workspace/domain/ObjectionManager.java new file mode 100644 index 0000000..887308b --- /dev/null +++ b/src/main/java/gymmi/workspace/domain/ObjectionManager.java @@ -0,0 +1,57 @@ +package gymmi.workspace.domain; + +import gymmi.exceptionhandler.exception.AlreadyExistException; +import gymmi.exceptionhandler.exception.InvalidStateException; +import gymmi.exceptionhandler.message.ErrorCode; +import gymmi.workspace.domain.entity.Objection; +import gymmi.workspace.domain.entity.Vote; +import gymmi.workspace.domain.entity.Worker; +import lombok.Getter; + +@Getter +public class ObjectionManager { + private final Objection objection; + private boolean isApproved; + + public ObjectionManager(Objection objection) { + this.objection = objection; + this.isApproved = false; + } + + public Vote createVote(Worker worker, boolean isApproved) { + if (!objection.isInProgress()) { + throw new InvalidStateException(ErrorCode.ALREADY_CLOSED_OBJECTION); + } + if (objection.hasVoteBy(worker)) { + throw new AlreadyExistException(ErrorCode.ALREADY_VOTED); + } + return new Vote(worker, objection, isApproved); + } + + public boolean closeIfOnMajorityOrDone(int workerCount) { + int majority = getMajority(workerCount); + if (objection.getApprovalCount() >= majority) { + objection.close(); + isApproved = true; + return true; + } + if (objection.getRejectionCount() >= majority) { + objection.close(); + return true; + } + if (objection.getVoteCount() >= workerCount) { + objection.close(); + return true; + } + return false; + } + + + private int getMajority(int workerCount) { + if (workerCount % 2 == 0) { + return (workerCount / 2) + 1; + } + return (int) Math.round(workerCount / 2.0); + } + +} diff --git a/src/main/java/gymmi/workspace/domain/WorkspaceProgressManager.java b/src/main/java/gymmi/workspace/domain/WorkspaceProgressManager.java index 50a585c..3bdc341 100644 --- a/src/main/java/gymmi/workspace/domain/WorkspaceProgressManager.java +++ b/src/main/java/gymmi/workspace/domain/WorkspaceProgressManager.java @@ -4,12 +4,9 @@ import gymmi.exceptionhandler.exception.InvalidStateException; import gymmi.exceptionhandler.message.ErrorCode; -import gymmi.workspace.domain.entity.Mission; -import gymmi.workspace.domain.entity.Worker; -import gymmi.workspace.domain.entity.WorkoutHistory; -import gymmi.workspace.domain.entity.WorkoutProof; -import gymmi.workspace.domain.entity.WorkoutRecord; -import gymmi.workspace.domain.entity.Workspace; +import gymmi.workspace.domain.entity.*; +import gymmi.workspace.domain.entity.WorkoutConfirmation; + import java.util.Collections; import java.util.List; import java.util.Map; @@ -34,7 +31,7 @@ private Workspace validateStatus(Workspace workspace) { return workspace; } - public WorkoutHistory doWorkout(Worker worker, Map workouts, WorkoutProof workoutProof) { + public WorkoutHistory doWorkout(Worker worker, Map workouts, WorkoutConfirmation workoutProof) { if (!worker.isJoinedIn(workspace)) { throw new InvalidStateException(NOT_JOINED_WORKSPACE); } diff --git a/src/main/java/gymmi/workspace/domain/entity/FavoriteMission.java b/src/main/java/gymmi/workspace/domain/entity/FavoriteMission.java index f04a6d5..009df71 100644 --- a/src/main/java/gymmi/workspace/domain/entity/FavoriteMission.java +++ b/src/main/java/gymmi/workspace/domain/entity/FavoriteMission.java @@ -1,13 +1,7 @@ package gymmi.workspace.domain.entity; import gymmi.entity.TimeEntity; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; +import jakarta.persistence.*; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -35,4 +29,5 @@ public FavoriteMission(Worker worker, Mission mission) { this.worker = worker; this.mission = mission; } + } diff --git a/src/main/java/gymmi/workspace/domain/entity/Objection.java b/src/main/java/gymmi/workspace/domain/entity/Objection.java new file mode 100644 index 0000000..387f0dc --- /dev/null +++ b/src/main/java/gymmi/workspace/domain/entity/Objection.java @@ -0,0 +1,93 @@ +package gymmi.workspace.domain.entity; + +import gymmi.entity.TimeEntity; +import gymmi.exceptionhandler.exception.NotHavePermissionException; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static gymmi.exceptionhandler.message.ErrorCode.NO_WORKOUT_HISTORY_EXIST_IN_WORKSPACE; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode(of = {"id"}, callSuper = false) +@Getter +public class Objection extends TimeEntity { + + private static final int PERIOD_HOUR = 24; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @JoinColumn(name = "worker_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private Worker subject; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "workout_confirmation_id", nullable = false, unique = true) + private WorkoutConfirmation workoutConfirmation; + + @Column(nullable = false) + private String reason; + + @Column(nullable = false) + private boolean isInProgress; + + @OneToMany(mappedBy = "objection") + private List votes = new ArrayList<>(); + + @Builder + public Objection(Worker subject, WorkoutConfirmation workoutConfirmation, String reason) { + this.subject = subject; + this.workoutConfirmation = workoutConfirmation; + this.reason = reason; + this.isInProgress = true; + } + + public boolean hasVoteBy(Worker worker) { + return votes.stream() + .map(vote -> vote.getWorker()) + .toList() + .contains(worker); + } + + public int getApprovalCount() { + return votes.stream() + .filter(vote -> vote.getIsApproved() == true) + .toList().size(); + } + + public int getVoteCount() { + return votes.size(); + } + + public int getRejectionCount() { + return votes.stream() + .filter(vote -> vote.getIsApproved() == false) + .toList().size(); + } + + public void close() { + this.isInProgress = false; + } + + public void canBeReadIn(Workspace workspace) { + if (!subject.isJoinedIn(workspace)) { + throw new NotHavePermissionException(NO_WORKOUT_HISTORY_EXIST_IN_WORKSPACE); + } + } + + public void add(Vote vote) { + votes.add(vote); + } + + public LocalDateTime getDeadline() { + return getCreatedAt().plusHours(PERIOD_HOUR); + } +} + + diff --git a/src/main/java/gymmi/workspace/domain/entity/Vote.java b/src/main/java/gymmi/workspace/domain/entity/Vote.java new file mode 100644 index 0000000..7008c38 --- /dev/null +++ b/src/main/java/gymmi/workspace/domain/entity/Vote.java @@ -0,0 +1,45 @@ +package gymmi.workspace.domain.entity; + +import gymmi.entity.TimeEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"worker_id", "tackle_id"})}) +@EqualsAndHashCode(of = {"id"}, callSuper = false) +@Getter +public class Vote extends TimeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @JoinColumn(name = "worker_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private Worker worker; + + @JoinColumn(name = "objection_id", nullable = false) + @ManyToOne(fetch = FetchType.LAZY) + private Objection objection; + + @Column(nullable = false) + private Boolean isApproved; + + public Vote(Worker worker, Objection objection, Boolean isApproved) { + this.worker = worker; + this.objection = objection; + this.isApproved = isApproved; + setRelations(objection); + } + + private void setRelations(Objection objection) { + objection.add(this); + } + +} + + diff --git a/src/main/java/gymmi/workspace/domain/entity/WorkoutProof.java b/src/main/java/gymmi/workspace/domain/entity/WorkoutConfirmation.java similarity index 54% rename from src/main/java/gymmi/workspace/domain/entity/WorkoutProof.java rename to src/main/java/gymmi/workspace/domain/entity/WorkoutConfirmation.java index ed60be7..ad0b8b6 100644 --- a/src/main/java/gymmi/workspace/domain/entity/WorkoutProof.java +++ b/src/main/java/gymmi/workspace/domain/entity/WorkoutConfirmation.java @@ -1,13 +1,6 @@ package gymmi.workspace.domain.entity; -import gymmi.exceptionhandler.exception.InvalidRangeException; -import gymmi.exceptionhandler.exception.NotHavePermissionException; -import gymmi.exceptionhandler.message.ErrorCode; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; +import jakarta.persistence.*; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -17,7 +10,7 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) @EqualsAndHashCode(of = {"id"}) @Getter -public class WorkoutProof { +public class WorkoutConfirmation { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -29,7 +22,7 @@ public class WorkoutProof { @Column(nullable = false) private String comment; - public WorkoutProof(String filename, String comment) { + public WorkoutConfirmation(String filename, String comment) { this.filename = filename; this.comment = comment; } diff --git a/src/main/java/gymmi/workspace/domain/entity/WorkoutHistory.java b/src/main/java/gymmi/workspace/domain/entity/WorkoutHistory.java index 2e4ffbb..ccafc58 100644 --- a/src/main/java/gymmi/workspace/domain/entity/WorkoutHistory.java +++ b/src/main/java/gymmi/workspace/domain/entity/WorkoutHistory.java @@ -1,27 +1,18 @@ package gymmi.workspace.domain.entity; -import static gymmi.exceptionhandler.message.ErrorCode.NO_WORKOUT_HISTORY_EXIST_IN_WORKSPACE; - import gymmi.entity.TimeEntity; import gymmi.exceptionhandler.exception.NotHavePermissionException; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OneToOne; -import java.util.ArrayList; -import java.util.List; +import jakarta.persistence.*; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; +import java.util.ArrayList; +import java.util.List; + +import static gymmi.exceptionhandler.message.ErrorCode.NO_WORKOUT_HISTORY_EXIST_IN_WORKSPACE; + @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -36,9 +27,9 @@ public class WorkoutHistory extends TimeEntity { @ManyToOne(fetch = FetchType.LAZY) private Worker worker; - @JoinColumn(name = "workout_proof_id", nullable = false) + @JoinColumn(name = "workout_confirmation_id", nullable = false, unique = true) @OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE}) - private WorkoutProof workoutProof; + private WorkoutConfirmation workoutConfirmation; @Column(nullable = false) private boolean isApproved; @@ -49,13 +40,13 @@ public class WorkoutHistory extends TimeEntity { @OneToMany(mappedBy = "workoutHistory", cascade = {CascadeType.PERSIST, CascadeType.REMOVE}) private List workoutRecords = new ArrayList<>(); - public WorkoutHistory(Worker worker, List workoutRecords, WorkoutProof workoutProof) { + public WorkoutHistory(Worker worker, List workoutRecords, WorkoutConfirmation workoutConfirmation) { this.worker = worker; this.workoutRecords = new ArrayList<>(workoutRecords); setRelations(workoutRecords); this.isApproved = true; this.totalScore = calculateSum(); - this.workoutProof = workoutProof; + this.workoutConfirmation = workoutConfirmation; } private void setRelations(List workoutRecords) { @@ -68,6 +59,10 @@ public void apply() { worker.addWorkingScore(totalScore); } + public void cancel() { + isApproved = false; + } + public int getSum() { return workoutRecords.stream() .map(WorkoutRecord::getSum) diff --git a/src/main/java/gymmi/workspace/repository/ObjectionRepository.java b/src/main/java/gymmi/workspace/repository/ObjectionRepository.java new file mode 100644 index 0000000..d147d25 --- /dev/null +++ b/src/main/java/gymmi/workspace/repository/ObjectionRepository.java @@ -0,0 +1,24 @@ +package gymmi.workspace.repository; + +import gymmi.exceptionhandler.exception.NotFoundException; +import gymmi.exceptionhandler.message.ErrorCode; +import gymmi.workspace.domain.entity.Objection; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.Optional; + +public interface ObjectionRepository extends JpaRepository { + + @Query("select o from Objection o where o.workoutConfirmation.id =:workoutConfirmationId") + Optional findByWorkoutConfirmationId(Long workoutConfirmationId); + + default Objection getByObjectionId(Long objectionId) { + Objection objection = findById(objectionId) + .orElseThrow(() -> new NotFoundException(ErrorCode.NOT_FOUND_OBJECTION)); + return objection; + } + + + +} diff --git a/src/main/java/gymmi/workspace/repository/VoteRepository.java b/src/main/java/gymmi/workspace/repository/VoteRepository.java new file mode 100644 index 0000000..0c0cc81 --- /dev/null +++ b/src/main/java/gymmi/workspace/repository/VoteRepository.java @@ -0,0 +1,7 @@ +package gymmi.workspace.repository; + +import gymmi.workspace.domain.entity.Vote; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VoteRepository extends JpaRepository { +} diff --git a/src/main/java/gymmi/workspace/repository/WorkoutHistoryRepository.java b/src/main/java/gymmi/workspace/repository/WorkoutHistoryRepository.java index 0ffe72d..314f42a 100644 --- a/src/main/java/gymmi/workspace/repository/WorkoutHistoryRepository.java +++ b/src/main/java/gymmi/workspace/repository/WorkoutHistoryRepository.java @@ -2,18 +2,37 @@ import gymmi.exceptionhandler.legacy.NotFoundResourcesException; import gymmi.workspace.domain.entity.WorkoutHistory; -import java.util.List; +import gymmi.workspace.repository.custom.WorkoutHistoryCustomRepository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -public interface WorkoutHistoryRepository extends JpaRepository { +import java.util.List; +import java.util.Optional; + +public interface WorkoutHistoryRepository extends JpaRepository, WorkoutHistoryCustomRepository { @Query("select w from WorkoutHistory w join fetch w.workoutRecords where w.worker.id =:workerId") List getAllByWorkerId(Long workerId); - default WorkoutHistory getById(Long workoutHistoryId) { + default WorkoutHistory getByWorkoutHistoryId(Long workoutHistoryId) { WorkoutHistory workoutHistory = findById(workoutHistoryId) .orElseThrow(() -> new NotFoundResourcesException("해당 운동 기록이 존재하지 않아요.")); return workoutHistory; } + + @Query("select w from WorkoutHistory w join fetch w.workoutConfirmation wf join fetch w.worker where wf.id =:workoutProofId") + Optional findByWorkoutConfirmationId(Long workoutProofId); + + + default WorkoutHistory getByWorkoutConfirmationId(Long workoutConfirmationId) { + WorkoutHistory workoutHistory = findByWorkoutConfirmationId(workoutConfirmationId) + .orElseThrow(() -> new NotFoundResourcesException("해당 운동 기록이 존재하지 않아요.")); + return workoutHistory; + } + + default WorkoutHistory getByObjectionId(Long objectionId) { + WorkoutHistory workoutHistory = findByWorkoutConfirmationId(objectionId) + .orElseThrow(() -> new NotFoundResourcesException("해당 운동 기록이 존재하지 않아요.")); + return workoutHistory; + } } diff --git a/src/main/java/gymmi/workspace/repository/custom/WorkoutHistoryCustomRepository.java b/src/main/java/gymmi/workspace/repository/custom/WorkoutHistoryCustomRepository.java new file mode 100644 index 0000000..dffb653 --- /dev/null +++ b/src/main/java/gymmi/workspace/repository/custom/WorkoutHistoryCustomRepository.java @@ -0,0 +1,13 @@ +package gymmi.workspace.repository.custom; + + +import gymmi.workspace.domain.entity.WorkoutHistory; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface WorkoutHistoryCustomRepository { + + List getAllByWorkspaceId(Long workspaceId, Pageable pageable); + +} diff --git a/src/main/java/gymmi/workspace/repository/custom/WorkoutHistoryCustomRepositoryImpl.java b/src/main/java/gymmi/workspace/repository/custom/WorkoutHistoryCustomRepositoryImpl.java new file mode 100644 index 0000000..94c4377 --- /dev/null +++ b/src/main/java/gymmi/workspace/repository/custom/WorkoutHistoryCustomRepositoryImpl.java @@ -0,0 +1,32 @@ +package gymmi.workspace.repository.custom; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import gymmi.workspace.domain.entity.QWorkoutConfirmation; +import gymmi.workspace.domain.entity.WorkoutHistory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +import static gymmi.workspace.domain.entity.QWorker.worker; +import static gymmi.workspace.domain.entity.QWorkoutConfirmation.workoutConfirmation; +import static gymmi.workspace.domain.entity.QWorkoutHistory.workoutHistory; + +@RequiredArgsConstructor +public class WorkoutHistoryCustomRepositoryImpl implements WorkoutHistoryCustomRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List getAllByWorkspaceId(Long workspaceId, Pageable pageable) { + return jpaQueryFactory.select(workoutHistory) + .from(workoutHistory) + .join(workoutHistory.workoutConfirmation, workoutConfirmation).fetchJoin() + .join(workoutHistory.worker, worker).fetchJoin() + .where(worker.workspace.id.eq(workspaceId)) + .orderBy(workoutHistory.createdAt.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } +} diff --git a/src/main/java/gymmi/workspace/repository/custom/WorkspaceCustomRepositoryImpl.java b/src/main/java/gymmi/workspace/repository/custom/WorkspaceCustomRepositoryImpl.java index 2758be3..77d1433 100644 --- a/src/main/java/gymmi/workspace/repository/custom/WorkspaceCustomRepositoryImpl.java +++ b/src/main/java/gymmi/workspace/repository/custom/WorkspaceCustomRepositoryImpl.java @@ -18,7 +18,6 @@ import org.springframework.stereotype.Repository; @RequiredArgsConstructor -@Repository public class WorkspaceCustomRepositoryImpl implements WorkspaceCustomRepository { private final JPAQueryFactory jpaQueryFactory; diff --git a/src/main/java/gymmi/workspace/request/ObjectionRequest.java b/src/main/java/gymmi/workspace/request/ObjectionRequest.java new file mode 100644 index 0000000..e239bde --- /dev/null +++ b/src/main/java/gymmi/workspace/request/ObjectionRequest.java @@ -0,0 +1,17 @@ +package gymmi.workspace.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class ObjectionRequest { + + @NotBlank + private String reason; + + public ObjectionRequest(String reason) { + this.reason = reason; + } +} diff --git a/src/main/java/gymmi/workspace/request/VoteRequest.java b/src/main/java/gymmi/workspace/request/VoteRequest.java new file mode 100644 index 0000000..eeb0925 --- /dev/null +++ b/src/main/java/gymmi/workspace/request/VoteRequest.java @@ -0,0 +1,17 @@ +package gymmi.workspace.request; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class VoteRequest { + + @NotBlank + private Boolean willApprove; + + public VoteRequest(Boolean willApprove) { + this.willApprove = willApprove; + } +} diff --git a/src/main/java/gymmi/workspace/response/FavoriteMissionResponse.java b/src/main/java/gymmi/workspace/response/FavoriteMissionResponse.java new file mode 100644 index 0000000..100f712 --- /dev/null +++ b/src/main/java/gymmi/workspace/response/FavoriteMissionResponse.java @@ -0,0 +1,24 @@ +package gymmi.workspace.response; + +import gymmi.workspace.domain.entity.Mission; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) // for test +public class FavoriteMissionResponse { + + private Long id; + private String mission; + private Integer score; + private Boolean isFavorite; + + public FavoriteMissionResponse(Mission mission) { + this.id = mission.getId(); + this.mission = mission.getName(); + this.score = mission.getScore(); + this.isFavorite = true; + } + +} diff --git a/src/main/java/gymmi/workspace/response/MissionResponse.java b/src/main/java/gymmi/workspace/response/MissionResponse.java index 017e56c..5b3f8c9 100644 --- a/src/main/java/gymmi/workspace/response/MissionResponse.java +++ b/src/main/java/gymmi/workspace/response/MissionResponse.java @@ -12,11 +12,13 @@ public class MissionResponse { private Long id; private String mission; private Integer score; + private Boolean isFavorite; - public MissionResponse(Mission mission) { + public MissionResponse(Mission mission, boolean isFavorite) { this.id = mission.getId(); this.mission = mission.getName(); this.score = mission.getScore(); + this.isFavorite = isFavorite; } } diff --git a/src/main/java/gymmi/workspace/response/ObjectionResponse.java b/src/main/java/gymmi/workspace/response/ObjectionResponse.java new file mode 100644 index 0000000..5d65717 --- /dev/null +++ b/src/main/java/gymmi/workspace/response/ObjectionResponse.java @@ -0,0 +1,76 @@ +package gymmi.workspace.response; + +import gymmi.workspace.domain.entity.Objection; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class ObjectionResponse { + + private LocalDateTime deadline; + private Boolean inInProgress; + private String reason; + private Integer voteParticipationCount; + private Boolean voteCompletion; + private Integer approvalCount; + private Integer rejectionCount; + private Boolean confirmationCompletion; + + @Builder + public ObjectionResponse( + LocalDateTime deadline, Boolean inInProgress, String reason, + Integer voteParticipationCount, Boolean voteCompletion, + Integer approvalCount, Integer rejectionCount, Boolean confirmationCompletion + ) { + this.deadline = deadline; + this.inInProgress = inInProgress; + this.reason = reason; + this.voteParticipationCount = voteParticipationCount; + this.voteCompletion = voteCompletion; + this.approvalCount = approvalCount; + this.rejectionCount = rejectionCount; + this.confirmationCompletion = confirmationCompletion; + } + + public static ObjectionResponse closedObjection(Objection objection, boolean voteCompletion, boolean confirmationCompletion) { + return ObjectionResponse.builder() + .deadline(objection.getDeadline()) + .inInProgress(false) + .reason(objection.getReason()) + .voteCompletion(voteCompletion) + .voteParticipationCount(objection.getVoteCount()) + .approvalCount(objection.getApprovalCount()) + .rejectionCount(objection.getRejectionCount()) + .confirmationCompletion(confirmationCompletion) + .build(); + } + + public static ObjectionResponse objectionInProgressWithVoteInCompletion(Objection objection) { + return ObjectionResponse.builder() + .deadline(objection.getDeadline()) + .inInProgress(true) + .reason(objection.getReason()) + .voteCompletion(false) + .voteParticipationCount(objection.getVoteCount()) + .approvalCount(null) + .rejectionCount(null) + .confirmationCompletion(null) + .build(); + } + + public static ObjectionResponse objectionInProgressWithVoteCompletion(Objection objection) { + return ObjectionResponse.builder() + .deadline(objection.getDeadline()) + .inInProgress(true) + .reason(objection.getReason()) + .voteCompletion(true) + .voteParticipationCount(objection.getVoteCount()) + .approvalCount(objection.getApprovalCount()) + .rejectionCount(objection.getRejectionCount()) + .confirmationCompletion(null) + .build(); + } + +} diff --git a/src/main/java/gymmi/workspace/response/WorkoutConfirmationDetailResponse.java b/src/main/java/gymmi/workspace/response/WorkoutConfirmationDetailResponse.java new file mode 100644 index 0000000..d668f16 --- /dev/null +++ b/src/main/java/gymmi/workspace/response/WorkoutConfirmationDetailResponse.java @@ -0,0 +1,32 @@ +package gymmi.workspace.response; + +import gymmi.entity.User; +import gymmi.workspace.domain.entity.Objection; +import lombok.Getter; + +@Getter +public class WorkoutConfirmationDetailResponse { + + private final String nickname; + private final String loginId; + private final String profileImageUrl; + private final String workoutConfirmationImageUrl; + private final String comment; + private final Long objectionId; + + public WorkoutConfirmationDetailResponse(User user, String workoutConfirmationImageUrl, String comment, Objection objection) { + this.nickname = user.getNickname(); + this.loginId = user.getLoginId(); + this.profileImageUrl = user.getProfileImageName(); + this.workoutConfirmationImageUrl = workoutConfirmationImageUrl; + this.comment = comment; + this.objectionId = getObjectionId(objection); + } + + private Long getObjectionId(Objection objection) { + if (objection == null) { + return null; + } + return objection.getId(); + } +} diff --git a/src/main/java/gymmi/workspace/response/WorkoutConfirmationResponse.java b/src/main/java/gymmi/workspace/response/WorkoutConfirmationResponse.java new file mode 100644 index 0000000..b314583 --- /dev/null +++ b/src/main/java/gymmi/workspace/response/WorkoutConfirmationResponse.java @@ -0,0 +1,28 @@ +package gymmi.workspace.response; + +import gymmi.workspace.domain.entity.WorkoutHistory; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class WorkoutConfirmationResponse { + + private final Long workoutConfirmationId; + private final String nickname; + private final String profileImageUrl; + private final String workoutConfirmationImageUrl; + private final LocalDateTime createdAt; + private final String dayOfTheWeek; + + public WorkoutConfirmationResponse(WorkoutHistory workoutHistory, String workoutConfirmationImageUrl) { + this.workoutConfirmationId = workoutHistory.getWorkoutConfirmation().getId(); + this.nickname = workoutHistory.getWorker().getUser().getNickname(); + this.profileImageUrl = workoutHistory.getWorker().getUser().getProfileImageName(); + this.workoutConfirmationImageUrl = workoutConfirmationImageUrl; + this.createdAt = workoutHistory.getCreatedAt(); + this.dayOfTheWeek = createdAt.getDayOfWeek().name(); + } + + +} diff --git a/src/main/java/gymmi/workspace/service/WorkspaceCommandService.java b/src/main/java/gymmi/workspace/service/WorkspaceCommandService.java index 9cb3d82..1556733 100644 --- a/src/main/java/gymmi/workspace/service/WorkspaceCommandService.java +++ b/src/main/java/gymmi/workspace/service/WorkspaceCommandService.java @@ -5,40 +5,23 @@ import gymmi.exceptionhandler.exception.InvalidStateException; import gymmi.exceptionhandler.exception.NotHavePermissionException; import gymmi.exceptionhandler.message.ErrorCode; -import gymmi.workspace.domain.WorkerLeavedEvent; -import gymmi.workspace.domain.WorkspaceDrawManager; -import gymmi.workspace.domain.WorkspaceEditManager; -import gymmi.workspace.domain.WorkspaceInitializer; -import gymmi.workspace.domain.WorkspacePreparingManager; -import gymmi.workspace.domain.WorkspaceProgressManager; -import gymmi.workspace.domain.entity.FavoriteMission; -import gymmi.workspace.domain.entity.Mission; -import gymmi.workspace.domain.entity.Task; -import gymmi.workspace.domain.entity.Worker; -import gymmi.workspace.domain.entity.WorkoutHistory; -import gymmi.workspace.domain.entity.WorkoutProof; -import gymmi.workspace.domain.entity.Workspace; -import gymmi.workspace.repository.FavoriteMissionRepository; -import gymmi.workspace.repository.MissionRepository; -import gymmi.workspace.repository.WorkerRepository; -import gymmi.workspace.repository.WorkoutHistoryRepository; -import gymmi.workspace.repository.WorkspaceRepository; -import gymmi.workspace.request.CreatingWorkspaceRequest; -import gymmi.workspace.request.EditingIntroductionOfWorkspaceRequest; -import gymmi.workspace.request.JoiningWorkspaceRequest; -import gymmi.workspace.request.WorkingMissionInWorkspaceRequest; -import gymmi.workspace.request.WorkoutRequest; +import gymmi.workspace.domain.*; +import gymmi.workspace.domain.entity.*; +import gymmi.workspace.repository.*; +import gymmi.workspace.request.*; import gymmi.workspace.response.OpeningTasksBoxResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor +@Transactional public class WorkspaceCommandService { private final WorkspaceRepository workspaceRepository; @@ -46,6 +29,8 @@ public class WorkspaceCommandService { private final MissionRepository missionRepository; private final WorkoutHistoryRepository workoutHistoryRepository; private final FavoriteMissionRepository favoriteMissionRepository; + private final ObjectionRepository objectionRepository; + private final VoteRepository voteRepository; @Transactional // 중복 요청 @@ -128,7 +113,7 @@ public Integer workMissionsInWorkspace( WorkoutHistory workoutHistory = workspaceProgressManager.doWorkout( worker, workouts, - new WorkoutProof(workoutRequest.getImageUrl(), workoutRequest.getComment()) + new WorkoutConfirmation(workoutRequest.getImageUrl(), workoutRequest.getComment()) ); workoutHistory.apply(); @@ -194,4 +179,43 @@ private Worker validateIfWorkerIsInWorkspace(Long userId, Long workspaceId) { .orElseThrow(() -> new NotHavePermissionException(ErrorCode.NOT_JOINED_WORKSPACE)); } + public void objectToWorkoutConfirmation(User loginedUser, Long workspaceId, Long workoutConfirmationId, ObjectionRequest request) { + Workspace workspace = workspaceRepository.getWorkspaceById(workspaceId); + Worker worker = validateIfWorkerIsInWorkspace(loginedUser.getId(), workspaceId); + WorkoutHistory workoutHistory = workoutHistoryRepository.getByWorkoutConfirmationId(workoutConfirmationId); + workoutHistory.canBeReadIn(workspace); + if (objectionRepository.findByWorkoutConfirmationId(workoutConfirmationId).isPresent()) { + throw new AlreadyExistException(ErrorCode.ALREADY_OBJECTED); + } + Objection objection = Objection.builder() + .subject(worker) + .reason(request.getReason()) + .workoutConfirmation(workoutHistory.getWorkoutConfirmation()) + .build(); + objectionRepository.save(objection); + } + + public void voteToObjection(User loginedUser, Long workspaceId, Long objectionId, VoteRequest request) { + Workspace workspace = workspaceRepository.getWorkspaceById(workspaceId); + Worker worker = validateIfWorkerIsInWorkspace(loginedUser.getId(), workspaceId); + Objection objection = objectionRepository.getByObjectionId(objectionId); + objection.canBeReadIn(workspace); + + ObjectionManager objectionManager = new ObjectionManager(objection); + Vote vote = objectionManager.createVote(worker, request.getWillApprove()); + voteRepository.save(vote); + + List workers = workerRepository.getAllByWorkspaceId(workspaceId); + + if (objectionManager.closeIfOnMajorityOrDone(workers.size())) { + WorkoutHistory workoutHistory = workoutHistoryRepository.getByWorkoutConfirmationId(objection.getWorkoutConfirmation().getId()); + rejectWorkoutHistory(objectionManager, workoutHistory); + } + } + + private void rejectWorkoutHistory(ObjectionManager objectionManager, WorkoutHistory workoutHistory) { + if (objectionManager.isApproved()) { + workoutHistory.cancel(); + } + } } diff --git a/src/main/java/gymmi/workspace/service/WorkspaceQueryService.java b/src/main/java/gymmi/workspace/service/WorkspaceQueryService.java index bf04756..9eb46d2 100644 --- a/src/main/java/gymmi/workspace/service/WorkspaceQueryService.java +++ b/src/main/java/gymmi/workspace/service/WorkspaceQueryService.java @@ -3,39 +3,23 @@ import gymmi.entity.User; import gymmi.exceptionhandler.exception.NotHavePermissionException; import gymmi.exceptionhandler.message.ErrorCode; -import gymmi.workspace.domain.entity.FavoriteMission; -import gymmi.workspace.domain.entity.Mission; -import gymmi.workspace.domain.entity.WorkoutHistory; -import gymmi.workspace.domain.entity.Worker; +import gymmi.service.S3Service; import gymmi.workspace.domain.WorkoutMetric; -import gymmi.workspace.domain.entity.WorkoutRecord; -import gymmi.workspace.domain.entity.Workspace; import gymmi.workspace.domain.WorkspaceGateChecker; import gymmi.workspace.domain.WorkspaceStatus; -import gymmi.workspace.repository.FavoriteMissionRepository; -import gymmi.workspace.repository.MissionRepository; -import gymmi.workspace.repository.WorkoutHistoryRepository; -import gymmi.workspace.repository.WorkerRepository; -import gymmi.workspace.repository.WorkoutRecordRepository; -import gymmi.workspace.repository.WorkspaceRepository; -import gymmi.workspace.response.CheckingCreationOfWorkspaceResponse; -import gymmi.workspace.response.CheckingEntranceOfWorkspaceResponse; -import gymmi.workspace.response.InsideWorkspaceResponse; -import gymmi.workspace.response.JoinedWorkspaceResponse; -import gymmi.workspace.response.MatchingWorkspacePasswordResponse; -import gymmi.workspace.response.MissionResponse; -import gymmi.workspace.response.WorkoutContextResponse; -import gymmi.workspace.response.WorkoutRecordResponse; -import gymmi.workspace.response.WorkspaceIntroductionResponse; -import gymmi.workspace.response.WorkspaceResponse; -import java.util.List; -import java.util.Map; +import gymmi.workspace.domain.entity.*; +import gymmi.workspace.repository.*; +import gymmi.workspace.response.*; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -49,6 +33,9 @@ public class WorkspaceQueryService { private final WorkoutHistoryRepository workoutHistoryRepository; private final WorkoutRecordRepository workoutRecordRepository; private final FavoriteMissionRepository favoriteMissionRepository; + private final ObjectionRepository objectionRepository; + + private final S3Service s3Service; public WorkspaceIntroductionResponse getWorkspaceIntroduction(User loginedUser, Long workspaceId) { Workspace workspace = workspaceRepository.getWorkspaceById(workspaceId); @@ -90,11 +77,18 @@ public MatchingWorkspacePasswordResponse matchesWorkspacePassword(Long workspace } public List getMissionsInWorkspace(User loginedUser, Long workspaceId) { - validateIfWorkerIsInWorkspace(loginedUser.getId(), workspaceId); + Worker worker = validateIfWorkerIsInWorkspace(loginedUser.getId(), workspaceId); List missions = missionRepository.getAllByWorkspaceId(workspaceId); - return missions.stream() - .map(MissionResponse::new) + List favoriteMissions = favoriteMissionRepository.getAllByWorkerId(worker.getId()).stream() + .map(favoriteMission -> favoriteMission.getMission()) .toList(); + + List responses = new ArrayList<>(); + for (Mission mission : missions) { + boolean isFavorite = favoriteMissions.contains(mission); + responses.add(new MissionResponse(mission, isFavorite)); + } + return responses; } public WorkoutContextResponse getWorkoutContext( @@ -123,7 +117,7 @@ public List getWorkoutRecordsInWorkoutHistory( ) { Workspace workspace = workspaceRepository.getWorkspaceById(workspaceId); validateIfWorkerIsInWorkspace(loginedUser.getId(), workspace.getId()); - WorkoutHistory workoutHistory = workoutHistoryRepository.getById(workoutHistoryId); + WorkoutHistory workoutHistory = workoutHistoryRepository.getByWorkoutHistoryId(workoutHistoryId); workoutHistory.canBeReadIn(workspace); List workoutRecords = workoutRecordRepository.getAllByWorkoutHistoryId(workoutHistoryId); return workoutRecords.stream() @@ -159,15 +153,63 @@ public InsideWorkspaceResponse enterWorkspace(User logiendUser, Long workspaceId return new InsideWorkspaceResponse(workspace, workers, achievementScore, logiendUser); } - public List getFavoriteMissions(User user, Long workspaceId) { + public List getFavoriteMissions(User loginedUser, Long workspaceId) { Workspace workspace = workspaceRepository.getWorkspaceById(workspaceId); - Worker worker = validateIfWorkerIsInWorkspace(user.getId(), workspace.getId()); + Worker worker = validateIfWorkerIsInWorkspace(loginedUser.getId(), workspace.getId()); List favoriteMissions = favoriteMissionRepository.getAllByWorkerId(worker.getId()); - return favoriteMissions.stream() .map(FavoriteMission::getMission) - .map(MissionResponse::new) + .map(FavoriteMissionResponse::new) .toList(); } + public List getWorkoutConfirmations(User loginedUser, Long workspaceId, int page) { + Workspace workspace = workspaceRepository.getWorkspaceById(workspaceId); + validateIfWorkerIsInWorkspace(loginedUser.getId(), workspace.getId()); + Pageable pageable = PageRequest.of(page, DEFAULT_PAGE_SIZE); + List workoutHistories = workoutHistoryRepository.getAllByWorkspaceId(workspace.getId(), pageable); + + List responses = new ArrayList<>(); + for (WorkoutHistory workoutHistory : workoutHistories) { + WorkoutConfirmation workoutConfirmation = workoutHistory.getWorkoutConfirmation(); + String imagePresignedUrl = s3Service.getPresignedUrl(workoutConfirmation.getFilename()); + responses.add(new WorkoutConfirmationResponse(workoutHistory, imagePresignedUrl)); + } + return responses; + } + + public WorkoutConfirmationDetailResponse getWorkoutConfirmation(User loginedUser, Long workspaceId, Long workoutConfirmationId) { + Workspace workspace = workspaceRepository.getWorkspaceById(workspaceId); + validateIfWorkerIsInWorkspace(loginedUser.getId(), workspace.getId()); + WorkoutHistory workoutHistory = workoutHistoryRepository.getByWorkoutConfirmationId(workoutConfirmationId); + + workoutHistory.canBeReadIn(workspace); + WorkoutConfirmation workoutConfirmation = workoutHistory.getWorkoutConfirmation(); + + String imagePresignedUrl = s3Service.getPresignedUrl(workoutConfirmation.getFilename()); + Objection objection = objectionRepository.findByWorkoutConfirmationId(workoutConfirmationId) + .orElseGet(() -> null); + + return new WorkoutConfirmationDetailResponse(workoutHistory.getWorker().getUser(), imagePresignedUrl, workoutConfirmation.getComment(), objection); + } + + public ObjectionResponse getObjection(User loginedUser, Long workspaceId, Long objectionId) { + Workspace workspace = workspaceRepository.getWorkspaceById(workspaceId); + Worker worker = validateIfWorkerIsInWorkspace(loginedUser.getId(), workspace.getId()); + Objection objection = objectionRepository.getByObjectionId(objectionId); + objection.canBeReadIn(workspace); + + if (objection.isInProgress() && objection.hasVoteBy(worker)) { + return ObjectionResponse.objectionInProgressWithVoteCompletion(objection); + } + + if (objection.isInProgress() && !objection.hasVoteBy(worker)) { + return ObjectionResponse.objectionInProgressWithVoteInCompletion(objection); + } + + WorkoutHistory workoutHistory = workoutHistoryRepository.getByWorkoutConfirmationId(objection.getWorkoutConfirmation().getId()); + + workoutHistory.canBeReadIn(workspace); + return ObjectionResponse.closedObjection(objection, objection.hasVoteBy(worker), workoutHistory.isApproved()); + } } diff --git a/src/main/resources/db/migration/V241112__change_and_create_table.sql b/src/main/resources/db/migration/V241112__change_and_create_table.sql new file mode 100644 index 0000000..d2b6c7f --- /dev/null +++ b/src/main/resources/db/migration/V241112__change_and_create_table.sql @@ -0,0 +1,32 @@ +alter table workout_proof rename workout_confirmation; + +alter table workout_history drop foreign key workout_history_ibfk_2; +alter table workout_history change column workout_proof_id workout_confirmation_id bigint not null unique; +alter table workout_history + add foreign key (workout_confirmation_id) references workout_confirmation (id); + +create table objection +( + id bigint not null auto_increment primary key, + worker_id bigint not null, + workout_confirmation_id bigint not null unique, + reason varchar(255) not null, + is_in_progress boolean not null, + created_at timestamp(3) not null, + last_modified_at timestamp(3) not null, + foreign key (worker_id) references worker (id), + foreign key (workout_confirmation_id) references workout_confirmation (id) +) engine=InnoDB; + +create table vote +( + id bigint not null auto_increment primary key, + worker_id bigint not null, + objection_id bigint not null, + is_approved boolean not null, + created_at timestamp(3) not null, + last_modified_at timestamp(3) not null, + foreign key (worker_id) references worker (id), + foreign key (objection_id) references objection (id), + unique (worker_id, objection_id) +) engine=InnoDB; diff --git a/src/test/java/gymmi/helper/Persister.java b/src/test/java/gymmi/helper/Persister.java index 3ada154..5eeec90 100644 --- a/src/test/java/gymmi/helper/Persister.java +++ b/src/test/java/gymmi/helper/Persister.java @@ -1,28 +1,24 @@ package gymmi.helper; -import static org.instancio.Select.field; - import gymmi.entity.User; import gymmi.repository.UserRepository; import gymmi.workspace.domain.WorkspaceStatus; -import gymmi.workspace.domain.entity.Mission; -import gymmi.workspace.domain.entity.Task; -import gymmi.workspace.domain.entity.Worker; -import gymmi.workspace.domain.entity.WorkoutHistory; -import gymmi.workspace.domain.entity.WorkoutProof; -import gymmi.workspace.domain.entity.WorkoutRecord; -import gymmi.workspace.domain.entity.Workspace; +import gymmi.workspace.domain.entity.*; import gymmi.workspace.repository.WorkerRepository; import gymmi.workspace.repository.WorkspaceRepository; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; -import java.util.List; -import java.util.Map; import org.instancio.Instancio; import org.instancio.Select; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.instancio.Select.field; + @Component @Transactional public class Persister { @@ -132,17 +128,62 @@ public WorkoutHistory persistWorkoutHistoryAndApply(Worker worker, Map new WorkoutRecord(workout.getKey(), workout.getValue())) .toList(); Worker managedWorker = entityManager.find(Worker.class, worker.getId()); - WorkoutProof workoutProof = Instancio.of(WorkoutProof.class) - .ignore(Select.field(WorkoutProof::getId)) + WorkoutConfirmation workoutConfirmation = Instancio.of(WorkoutConfirmation.class) + .ignore(Select.field(WorkoutConfirmation::getId)) .create(); - entityManager.persist(workoutProof); + entityManager.persist(workoutConfirmation); WorkoutHistory workoutHistory = new WorkoutHistory( - managedWorker, workoutRecords, workoutProof + managedWorker, workoutRecords, workoutConfirmation ); entityManager.persist(workoutHistory); workoutHistory.apply(); return workoutHistory; } + public WorkoutHistory persistWorkoutHistoryAndApply(Worker worker, Map workouts, WorkoutConfirmation workoutConfirmation) { + List workoutRecords = workouts.entrySet().stream() + .map(workout -> new WorkoutRecord(workout.getKey(), workout.getValue())) + .toList(); + Worker managedWorker = entityManager.find(Worker.class, worker.getId()); + WorkoutHistory workoutHistory = new WorkoutHistory( + managedWorker, workoutRecords, workoutConfirmation + ); + entityManager.persist(workoutHistory); + workoutHistory.apply(); + return workoutHistory; + } + + public Objection persistObjection(Worker subject, boolean isInProgress, WorkoutConfirmation workoutConfirmation) { + Objection objection = Instancio.of(Objection.class) + .set(field(Objection::getSubject), subject) + .set(field(Objection::isInProgress), isInProgress) + .set(field(Objection::getWorkoutConfirmation), workoutConfirmation) + .set(field(Objection::getVotes), new ArrayList<>()) + .ignore(field(Objection::getId)) + .create(); + entityManager.persist(objection); + return objection; + } + + public WorkoutConfirmation persistWorkoutConfirmation() { + WorkoutConfirmation workoutConfirmation = Instancio.of(WorkoutConfirmation.class) + .ignore(field(WorkoutConfirmation::getId)) + .create(); + entityManager.persist(workoutConfirmation); + return workoutConfirmation; + } + + public Vote persistVote(Worker worker, Objection objection, boolean isApproved) { + Vote vote = Instancio.of(Vote.class) + .set(field(Vote::getWorker), worker) + .set(field(Vote::getObjection), objection) + .set(field(Vote::getIsApproved), isApproved) + .ignore(field(Vote::getId)) + .create(); + objection.add(vote); + entityManager.persist(vote); + return vote; + } + } diff --git a/src/test/java/gymmi/workspace/domain/ObjectionManagerTest.java b/src/test/java/gymmi/workspace/domain/ObjectionManagerTest.java new file mode 100644 index 0000000..f90e1a1 --- /dev/null +++ b/src/test/java/gymmi/workspace/domain/ObjectionManagerTest.java @@ -0,0 +1,106 @@ +package gymmi.workspace.domain; + +import gymmi.exceptionhandler.message.ErrorCode; +import gymmi.workspace.domain.entity.Objection; +import gymmi.workspace.domain.entity.Vote; +import gymmi.workspace.domain.entity.Worker; +import org.instancio.Instancio; +import org.instancio.Select; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ObjectionManagerTest { + + @Test + void 이미_투표에_참여한_경우_예외가_발생한다() { + // given + Objection objection = getTackle(true); + Vote vote = getVote(objection); + addVoteToTackle(objection, vote); + + Worker worker = vote.getWorker(); + ObjectionManager tackleManager = new ObjectionManager(objection); + + // when, then + assertThatThrownBy(() -> tackleManager.createVote(worker, true)) + .hasMessage(ErrorCode.ALREADY_VOTED.getMessage()); + } + + @Test + void 종료된_투표에_투표하는_경우_예외가_발생한다() { + // given + Objection objection = getTackle(false); + Vote vote = getVote(objection); + addVoteToTackle(objection, vote); + + Worker worker = vote.getWorker(); + ObjectionManager tackleManager = new ObjectionManager(objection); + + // when, then + assertThatThrownBy(() -> tackleManager.createVote(worker, true)) + .hasMessage(ErrorCode.ALREADY_CLOSED_OBJECTION.getMessage()); + } + + @ParameterizedTest + @CsvSource(value = { + "4,3,1,true, true", + "4,2,1,false, false", + "5,2,2,false, false", + "4,2,2,true, false", + }) + void 투표에_따라_투표가_종료된다(int workerCount, int agreeCount, int disagreeCount, boolean isClosed, boolean isApproved) { + // given + Objection objection = getTackle(true); + List agreeVotes = getVotes(agreeCount, objection, true); + List disagreeVotes = getVotes(disagreeCount, objection, false); + agreeVotes.addAll(disagreeVotes); + addVoteToTackle(objection, agreeVotes); + + ObjectionManager tackleManager = new ObjectionManager(objection); + + // when + boolean result = tackleManager.closeIfOnMajorityOrDone(workerCount); + + // then + assertThat(!tackleManager.getObjection().isInProgress()).isEqualTo(isClosed); + assertThat(result).isEqualTo(isClosed); + assertThat(tackleManager.isApproved()).isEqualTo(isApproved); + } + + private List getVotes(int size, Objection objection, boolean isApproved) { + List agreeVotes = Instancio.ofList(Vote.class) + .size(size) + .set(Select.field(Vote::getObjection), objection) + .set(Select.field(Vote::getIsApproved), isApproved) + .create(); + return agreeVotes; + } + + private Vote getVote(Objection objection) { + return Instancio.of(Vote.class) + .set(Select.field(Vote::getObjection), objection) + .create(); + } + + private void addVoteToTackle(Objection objection, Vote... vote) { + ReflectionTestUtils.setField(objection, "votes", List.of(vote)); + } + + private void addVoteToTackle(Objection objection, List votes) { + ReflectionTestUtils.setField(objection, "votes", new ArrayList<>(votes)); + } + + private Objection getTackle(boolean isOpen) { + return Instancio.of(Objection.class) + .set(Select.field(Objection::isInProgress), isOpen) + .create(); + } +} diff --git a/src/test/java/gymmi/workspace/domain/WorkoutHistoryTest.java b/src/test/java/gymmi/workspace/domain/WorkoutHistoryTest.java index 51ecaf8..c9c781e 100644 --- a/src/test/java/gymmi/workspace/domain/WorkoutHistoryTest.java +++ b/src/test/java/gymmi/workspace/domain/WorkoutHistoryTest.java @@ -5,12 +5,9 @@ import gymmi.entity.User; import gymmi.exceptionhandler.message.ErrorCode; -import gymmi.workspace.domain.entity.Task; -import gymmi.workspace.domain.entity.Worker; -import gymmi.workspace.domain.entity.WorkoutHistory; -import gymmi.workspace.domain.entity.WorkoutProof; -import gymmi.workspace.domain.entity.WorkoutRecord; -import gymmi.workspace.domain.entity.Workspace; +import gymmi.workspace.domain.entity.*; +import gymmi.workspace.domain.entity.WorkoutConfirmation; + import java.util.List; import org.instancio.Instancio; import org.instancio.Select; @@ -26,7 +23,7 @@ class WorkoutHistoryTest { Task task = Instancio.create(Task.class); List workoutRecords = Instancio.ofList(WorkoutRecord.class).size(2).create(); Worker worker = new Worker(user, workspace, task); - WorkoutProof workoutProof = Instancio.of(WorkoutProof.class).create(); + WorkoutConfirmation workoutProof = Instancio.of(WorkoutConfirmation.class).create(); WorkoutHistory workoutHistory = new WorkoutHistory(worker, workoutRecords, workoutProof); assertThat(worker.getContributedScore()).isEqualTo(0); @@ -45,7 +42,7 @@ class WorkoutHistoryTest { .set(Select.field(Worker::getWorkspace), workspace) .create(); List workoutRecords = Instancio.ofList(WorkoutRecord.class).size(2).create(); - WorkoutProof workoutProof = Instancio.of(WorkoutProof.class).create(); + WorkoutConfirmation workoutProof = Instancio.of(WorkoutConfirmation.class).create(); WorkoutHistory workoutHistory = new WorkoutHistory(worker, workoutRecords, workoutProof); Workspace workspace1 = Instancio.of(Workspace.class) .filter(Select.field(Workspace::getId), (Long id) -> !id.equals(workspace.getId())) diff --git a/src/test/java/gymmi/workspace/domain/WorkspaceProgressManagerTest.java b/src/test/java/gymmi/workspace/domain/WorkspaceProgressManagerTest.java index 216cf2a..34f748d 100644 --- a/src/test/java/gymmi/workspace/domain/WorkspaceProgressManagerTest.java +++ b/src/test/java/gymmi/workspace/domain/WorkspaceProgressManagerTest.java @@ -4,11 +4,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import gymmi.exceptionhandler.message.ErrorCode; -import gymmi.workspace.domain.entity.Mission; -import gymmi.workspace.domain.entity.Worker; -import gymmi.workspace.domain.entity.WorkoutHistory; -import gymmi.workspace.domain.entity.WorkoutProof; -import gymmi.workspace.domain.entity.Workspace; +import gymmi.workspace.domain.entity.*; +import gymmi.workspace.domain.entity.WorkoutConfirmation; + import java.util.Arrays; import java.util.List; import java.util.Map; @@ -46,7 +44,7 @@ class 운동_하기 { .filter(Select.field(Worker::getWorkspace), (Workspace ws) -> !ws.equals(workspace)) .create(); Map workouts = Map.of(missions.get(0), 1); - WorkoutProof workoutProof = Instancio.of(WorkoutProof.class).create(); + WorkoutConfirmation workoutProof = Instancio.of(WorkoutConfirmation.class).create(); // when, then assertThatThrownBy( @@ -66,7 +64,7 @@ class 운동_하기 { .filter(Select.field(Mission::getWorkspace), (Workspace ws) -> !ws.equals(workspace)) .create(); Map workouts = Map.of(mission, 1); - WorkoutProof workoutProof = Instancio.of(WorkoutProof.class).create(); + WorkoutConfirmation workoutProof = Instancio.of(WorkoutConfirmation.class).create(); // when, then assertThatThrownBy(() -> workspaceProgressManager.doWorkout(worker, workouts, workoutProof)) @@ -88,7 +86,7 @@ class 운동_하기 { Map workouts = Map.of(mission, missionCount, mission1, missionCount1); int sum = mission.getScore() * missionCount + mission1.getScore() * missionCount1; - WorkoutProof workoutProof = Instancio.of(WorkoutProof.class).create(); + WorkoutConfirmation workoutProof = Instancio.of(WorkoutConfirmation.class).create(); // when WorkoutHistory workoutHistory = workspaceProgressManager.doWorkout(worker, workouts, workoutProof); diff --git a/src/test/java/gymmi/workspace/service/WorkspaceCommandServiceTest.java b/src/test/java/gymmi/workspace/service/WorkspaceCommandServiceTest.java index c67ad3b..5922f7c 100644 --- a/src/test/java/gymmi/workspace/service/WorkspaceCommandServiceTest.java +++ b/src/test/java/gymmi/workspace/service/WorkspaceCommandServiceTest.java @@ -1,33 +1,25 @@ package gymmi.workspace.service; -import static gymmi.exceptionhandler.message.ErrorCode.EXCEED_MAX_JOINED_WORKSPACE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.instancio.Select.field; - import gymmi.entity.User; import gymmi.exceptionhandler.message.ErrorCode; import gymmi.workspace.domain.WorkspaceStatus; -import gymmi.workspace.domain.entity.Mission; -import gymmi.workspace.domain.entity.Task; -import gymmi.workspace.domain.entity.Worker; -import gymmi.workspace.domain.entity.Workspace; -import gymmi.workspace.repository.FavoriteMissionRepository; -import gymmi.workspace.repository.MissionRepository; -import gymmi.workspace.repository.WorkerRepository; -import gymmi.workspace.repository.WorkoutHistoryRepository; -import gymmi.workspace.repository.WorkspaceRepository; -import gymmi.workspace.request.CreatingWorkspaceRequest; -import gymmi.workspace.request.MissionRequest; -import gymmi.workspace.request.WorkingMissionInWorkspaceRequest; -import gymmi.workspace.request.WorkoutRequest; +import gymmi.workspace.domain.entity.*; +import gymmi.workspace.repository.*; +import gymmi.workspace.request.*; import jakarta.persistence.EntityManager; -import java.util.List; import org.instancio.Instancio; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; +import java.util.Map; + +import static gymmi.exceptionhandler.message.ErrorCode.EXCEED_MAX_JOINED_WORKSPACE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.instancio.Select.field; + class WorkspaceCommandServiceTest extends IntegrationTest { @Autowired @@ -42,6 +34,10 @@ class WorkspaceCommandServiceTest extends IntegrationTest { WorkoutHistoryRepository workoutHistoryRepository; @Autowired FavoriteMissionRepository favoriteMissionRepository; + @Autowired + VoteRepository voteRepository; + @Autowired + ObjectionRepository objectionRepository; @Autowired EntityManager entityManager; @@ -157,6 +153,56 @@ class 워크스페이스_생성 { assertThat(favoriteMissionRepository.findByWorkerIdAndMissionId(worker.getId(), mission.getId())).isEmpty(); } + @Test + void 운동인증에_이의_신청을_한다() { + // given + User creator = persister.persistUser(); + User user = persister.persistUser(); + Workspace workspace = persister.persistWorkspace(creator, WorkspaceStatus.IN_PROGRESS); + Worker creatorWorker = persister.persistWorker(creator, workspace); + Worker userWorker = persister.persistWorker(user, workspace); + Mission mission = persister.persistMission(workspace, 1); + Mission mission1 = persister.persistMission(workspace, 5); + WorkoutConfirmation workoutConfirmation = persister.persistWorkoutConfirmation(); + WorkoutHistory workoutHistory = persister.persistWorkoutHistoryAndApply(creatorWorker, Map.of(mission, 2, mission1, 2), workoutConfirmation); + ObjectionRequest request = new ObjectionRequest("이유"); + Long workoutConfirmationId = workoutHistory.getWorkoutConfirmation().getId(); + + // when + workspaceCommandService.objectToWorkoutConfirmation(user, workspace.getId(), workoutConfirmationId, request); + + // then + assertThat(objectionRepository.findByWorkoutConfirmationId(workoutConfirmationId)).isNotEmpty(); + } + + @Test + void 태클에_투표를_한다() { + // given + User creator = persister.persistUser(); + User user = persister.persistUser(); + User user1 = persister.persistUser(); + Workspace workspace = persister.persistWorkspace(creator, WorkspaceStatus.IN_PROGRESS, 100, 3); + Worker creatorWorker = persister.persistWorker(creator, workspace); + Worker userWorker = persister.persistWorker(user, workspace); + Worker user1Worker = persister.persistWorker(user1, workspace); + WorkoutConfirmation workoutConfirmation = persister.persistWorkoutConfirmation(); + Mission mission = persister.persistMission(workspace, 10); + persister.persistWorkoutHistoryAndApply(creatorWorker, Map.of(mission, 1), workoutConfirmation); + Objection objection = persister.persistObjection(userWorker, true, workoutConfirmation); + persister.persistVote(userWorker, objection, true); + persister.persistVote(creatorWorker, objection, false); + VoteRequest request = new VoteRequest(true); + + // when + workspaceCommandService.voteToObjection(user1, workspace.getId(), objection.getId(), request); + + // then + WorkoutHistory workoutHistory = workoutHistoryRepository.getByWorkoutConfirmationId(workoutConfirmation.getId()); + assertThat(voteRepository.findAll().size()).isEqualTo(3); + assertThat(objection.isInProgress()).isEqualTo(false); + assertThat(workoutHistory.isApproved()).isFalse(); + } + private List persistWorkspacesNotCompletedWithWorker(User user, int size) { List workspaces = Instancio.ofList(Workspace.class) .size(size) diff --git a/src/test/java/gymmi/workspace/service/WorkspaceQueryServiceTest.java b/src/test/java/gymmi/workspace/service/WorkspaceQueryServiceTest.java index d406f39..eac7f72 100644 --- a/src/test/java/gymmi/workspace/service/WorkspaceQueryServiceTest.java +++ b/src/test/java/gymmi/workspace/service/WorkspaceQueryServiceTest.java @@ -1,16 +1,22 @@ package gymmi.workspace.service; -import static org.assertj.core.api.Assertions.assertThat; - import gymmi.entity.User; +import gymmi.service.S3Service; import gymmi.workspace.domain.WorkspaceStatus; -import gymmi.workspace.domain.entity.Mission; -import gymmi.workspace.domain.entity.Worker; -import gymmi.workspace.domain.entity.Workspace; +import gymmi.workspace.domain.entity.*; +import gymmi.workspace.response.ObjectionResponse; +import gymmi.workspace.response.WorkoutConfirmationDetailResponse; +import gymmi.workspace.response.WorkoutConfirmationResponse; import gymmi.workspace.response.WorkoutContextResponse; -import java.util.Map; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; class WorkspaceQueryServiceTest extends IntegrationTest { @@ -18,6 +24,9 @@ class WorkspaceQueryServiceTest extends IntegrationTest { @Autowired WorkspaceQueryService workspaceQueryService; + @MockBean + S3Service s3Service; + @Test void 참여자의_운동_현황을_확인_한다() { // given @@ -47,4 +56,142 @@ class WorkspaceQueryServiceTest extends IntegrationTest { assertThat(response.getTotalWorkoutCount()).isEqualTo(2); } + @Test + void 워크스페이스의_운동_인증_목록을_확인_한다() { + // given + User creator = persister.persistUser(); + User user = persister.persistUser(); + Workspace workspace = persister.persistWorkspace(creator, WorkspaceStatus.IN_PROGRESS); + Worker creatorWorker = persister.persistWorker(creator, workspace); + Worker userWorker = persister.persistWorker(user, workspace); + Mission mission = persister.persistMission(workspace, 1); + Mission mission1 = persister.persistMission(workspace, 5); + WorkoutConfirmation workoutConfirmation = new WorkoutConfirmation("creator", "a"); + WorkoutConfirmation workoutConfirmation1 = new WorkoutConfirmation("user", "b"); + + WorkoutHistory workoutHistory = persister.persistWorkoutHistoryAndApply(creatorWorker, Map.of(mission, 2, mission1, 2), workoutConfirmation); + WorkoutHistory workoutHistory1 = persister.persistWorkoutHistoryAndApply(userWorker, Map.of(mission, 2, mission1, 2), workoutConfirmation1); + + // when + List responses = workspaceQueryService.getWorkoutConfirmations(user, workspace.getId(), 0); + + // then + assertThat(responses).hasSize(2); + WorkoutConfirmationResponse response = responses.get(0); + assertThat(response.getNickname()).isEqualTo(user.getNickname()); + assertThat(response.getProfileImageUrl()).isEqualTo(user.getProfileImageName()); + assertThat(response.getWorkoutConfirmationId()).isEqualTo(workoutHistory1.getWorkoutConfirmation().getId()); + WorkoutConfirmationResponse response1 = responses.get(1); + assertThat(response1.getNickname()).isEqualTo(creator.getNickname()); + assertThat(response1.getProfileImageUrl()).isEqualTo(creator.getProfileImageName()); + assertThat(response1.getWorkoutConfirmationId()).isEqualTo(workoutHistory.getWorkoutConfirmation().getId()); + } + + @Test + void 운동_인증_상세를_확인_한다() { + // given + User creator = persister.persistUser(); + User user = persister.persistUser(); + Workspace workspace = persister.persistWorkspace(creator, WorkspaceStatus.IN_PROGRESS); + Worker creatorWorker = persister.persistWorker(creator, workspace); + Worker userWorker = persister.persistWorker(user, workspace); + Mission mission = persister.persistMission(workspace, 1); + Mission mission1 = persister.persistMission(workspace, 5); + WorkoutConfirmation workoutConfirmation = new WorkoutConfirmation("creator", "a"); + WorkoutHistory workoutHistory = persister.persistWorkoutHistoryAndApply(creatorWorker, Map.of(mission, 2, mission1, 2), workoutConfirmation); + + // when + WorkoutConfirmationDetailResponse response = workspaceQueryService.getWorkoutConfirmation(user, workspace.getId(), workoutConfirmation.getId()); + + // then + assertThat(response.getComment()).isEqualTo("a"); + assertThat(response.getProfileImageUrl()).isEqualTo(creator.getProfileImageName()); + assertThat(response.getLoginId()).isEqualTo(creator.getLoginId()); + assertThat(response.getNickname()).isEqualTo(creator.getNickname()); + assertThat(response.getObjectionId()).isEqualTo(null); + } + + @Nested + class 이의_신청_확인 { + + @Test + void 투표_o_마감_x() { + // given + User creator = persister.persistUser(); + User user = persister.persistUser(); + Workspace workspace = persister.persistWorkspace(creator, WorkspaceStatus.IN_PROGRESS, 100, 5); + Worker creatorWorker = persister.persistWorker(creator, workspace); + Worker userWorker = persister.persistWorker(user, workspace); + WorkoutConfirmation workoutConfirmation = persister.persistWorkoutConfirmation(); + Objection objection = persister.persistObjection(userWorker, true, workoutConfirmation); + persister.persistVote(userWorker, objection, true); + persister.persistVote(creatorWorker, objection, false); + + // when + ObjectionResponse response = workspaceQueryService.getObjection(user, workspace.getId(), objection.getId()); + + // then + assertThat(response.getDeadline()).isEqualTo(objection.getCreatedAt().plusHours(24)); + assertThat(response.getInInProgress()).isTrue(); + assertThat(response.getVoteCompletion()).isTrue(); + assertThat(response.getApprovalCount()).isEqualTo(1); + assertThat(response.getRejectionCount()).isEqualTo(1); + assertThat(response.getVoteParticipationCount()).isEqualTo(2); + assertThat(response.getConfirmationCompletion()).isNull(); + } + + @Test + void 투표_x_마감_x() { + // given + User creator = persister.persistUser(); + User user = persister.persistUser(); + Workspace workspace = persister.persistWorkspace(creator, WorkspaceStatus.IN_PROGRESS, 100, 5); + Worker creatorWorker = persister.persistWorker(creator, workspace); + Worker userWorker = persister.persistWorker(user, workspace); + WorkoutConfirmation workoutConfirmation = persister.persistWorkoutConfirmation(); + Objection objection = persister.persistObjection(userWorker, true, workoutConfirmation); + persister.persistVote(creatorWorker, objection, false); + + // when + ObjectionResponse response = workspaceQueryService.getObjection(user, workspace.getId(), objection.getId()); + + // then + assertThat(response.getDeadline()).isEqualTo(objection.getCreatedAt().plusHours(24)); + assertThat(response.getInInProgress()).isTrue(); + assertThat(response.getVoteCompletion()).isFalse(); + assertThat(response.getApprovalCount()).isNull(); + assertThat(response.getRejectionCount()).isNull(); + assertThat(response.getVoteParticipationCount()).isEqualTo(1); + assertThat(response.getConfirmationCompletion()).isNull(); + } + + @Test + void 투표_x_마감_o() { + // given + User creator = persister.persistUser(); + User user = persister.persistUser(); + Workspace workspace = persister.persistWorkspace(creator, WorkspaceStatus.IN_PROGRESS, 100, 3); + Worker creatorWorker = persister.persistWorker(creator, workspace); + Worker userWorker = persister.persistWorker(user, workspace); + Mission mission = persister.persistMission(workspace, 10); + WorkoutConfirmation workoutConfirmation = persister.persistWorkoutConfirmation(); + persister.persistWorkoutHistoryAndApply(creatorWorker, Map.of(mission, 1), workoutConfirmation); + Objection objection = persister.persistObjection(userWorker, false, workoutConfirmation); + persister.persistVote(creatorWorker, objection, false); + ; + + // when + ObjectionResponse response = workspaceQueryService.getObjection(user, workspace.getId(), objection.getId()); + + // then + assertThat(response.getDeadline()).isEqualTo(objection.getCreatedAt().plusHours(24)); + assertThat(response.getInInProgress()).isFalse(); + assertThat(response.getVoteCompletion()).isFalse(); + assertThat(response.getApprovalCount()).isEqualTo(0); + assertThat(response.getRejectionCount()).isEqualTo(1); + assertThat(response.getVoteParticipationCount()).isEqualTo(1); + assertThat(response.getConfirmationCompletion()).isTrue(); + } + + } }