From 494d6c309d4185e306e2306def42dc22abb6266b Mon Sep 17 00:00:00 2001 From: yejin Date: Mon, 18 Aug 2025 23:20:26 +0900 Subject: [PATCH 01/17] =?UTF-8?q?Feat:=20=EC=9E=A5=EC=86=8C=20=ED=88=AC?= =?UTF-8?q?=ED=91=9C=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../teamcback/domain/place/entity/Place.java | 4 - .../vote/controller/VoteController.java | 89 ++++++++++++++ .../vote/dto/response/GetVoteOptionRes.java | 24 ++++ .../domain/vote/dto/response/GetVoteRes.java | 26 ++++ .../vote/dto/response/SaveVoteRecordRes.java | 9 ++ .../domain/vote/entity/VoteOption.java | 28 +++++ .../domain/vote/entity/VoteRecord.java | 37 ++++++ .../domain/vote/entity/VoteStatus.java | 10 ++ .../domain/vote/entity/VoteTopic.java | 30 +++++ .../vote/repository/VoteOptionRepository.java | 10 ++ .../vote/repository/VoteRecordRepository.java | 8 ++ .../vote/repository/VoteTopicRepository.java | 14 +++ .../domain/vote/service/VoteService.java | 114 ++++++++++++++++++ .../teamcback/global/response/ResultCode.java | 7 +- 14 files changed, 405 insertions(+), 5 deletions(-) create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteOptionRes.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/dto/response/SaveVoteRecordRes.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/entity/VoteTopic.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/repository/VoteOptionRepository.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/repository/VoteTopicRepository.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java diff --git a/src/main/java/devkor/com/teamcback/domain/place/entity/Place.java b/src/main/java/devkor/com/teamcback/domain/place/entity/Place.java index dc610d95..fb44b89e 100644 --- a/src/main/java/devkor/com/teamcback/domain/place/entity/Place.java +++ b/src/main/java/devkor/com/teamcback/domain/place/entity/Place.java @@ -1,15 +1,11 @@ package devkor.com.teamcback.domain.place.entity; -import static devkor.com.teamcback.domain.operatingtime.service.OperatingService.OPERATING_TIME_PATTERN; - -import devkor.com.teamcback.domain.operatingtime.entity.DayOfWeek; import devkor.com.teamcback.domain.place.dto.request.CreatePlaceReq; import devkor.com.teamcback.domain.place.dto.request.ModifyPlaceReq; import devkor.com.teamcback.domain.building.entity.Building; import devkor.com.teamcback.domain.common.BaseEntity; import devkor.com.teamcback.domain.routes.entity.Node; import jakarta.persistence.*; -import java.util.regex.Pattern; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; diff --git a/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java b/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java new file mode 100644 index 00000000..c0badab2 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java @@ -0,0 +1,89 @@ +package devkor.com.teamcback.domain.vote.controller; + +import devkor.com.teamcback.domain.vote.dto.response.GetVoteRes; +import devkor.com.teamcback.domain.vote.dto.response.SaveVoteRecordRes; +import devkor.com.teamcback.domain.vote.entity.VoteStatus; +import devkor.com.teamcback.domain.vote.service.VoteService; +import devkor.com.teamcback.global.response.CommonResponse; +import devkor.com.teamcback.global.security.UserDetailsImpl; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/votes") +public class VoteController { + private final VoteService voteService; + + /** + * 투표 리스트 조회 + * @param status 조회할 투표 상태 + */ + @Operation(summary = "투표 리스트 조회", description = "투표 리스트 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "Not Found", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + @GetMapping("") + public CommonResponse> getVoteList( + @Parameter(name = "status", description = "투표 상태", example = "OPEN") + @RequestParam(name = "status", required = false) VoteStatus status) { + return CommonResponse.success(voteService.getVoteList(status)); + } + + + /** + * 투표 정보 조회 + * @param voteTopicId 투표 주제 ID + */ + @Operation(summary = "투표 정보 조회", description = "투표 정보 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "Not Found", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + @GetMapping("/{voteTopicId}") + public CommonResponse getVote( + @Parameter(description = "투표 주제 ID", example = "1") + @PathVariable(name = "voteTopicId") Long voteTopicId) { + return CommonResponse.success(voteService.getVote(voteTopicId)); + } + + /** + * 투표 기록 저장 + * @param userDetail 사용자 정보 + * @param voteTopicId 투표 주제 ID + * @param voteOptionId 투표 옵션 ID + */ + @Operation(summary = "투표 저장", description = "투표 저장") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "Not Found", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + @PostMapping("/{voteTopicId}/records") + public CommonResponse saveVoteRecord( + @Parameter(description = "사용자정보") + @AuthenticationPrincipal UserDetailsImpl userDetail, + @Parameter(name = "voteTopicId", description = "투표 주제 ID", example = "1") + @PathVariable(name = "voteTopicId") Long voteTopicId, + @Parameter(name = "voteOptionId", description = "투표 옵션 ID", example = "1") + @RequestParam(name = "voteOptionId") Long voteOptionId, + @Parameter(name = "placeId", description = "투표 장소 ID", example = "1") + @RequestParam(name = "placeId", required = false) Long placeId + ) { + Long userId = userDetail == null ? null : userDetail.getUser().getUserId(); + return CommonResponse.success(voteService.saveVoteRecord(userId, voteTopicId, voteOptionId, placeId)); + } + +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteOptionRes.java b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteOptionRes.java new file mode 100644 index 00000000..b6e415b6 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteOptionRes.java @@ -0,0 +1,24 @@ +package devkor.com.teamcback.domain.vote.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Schema(description = "투표 항목 조회 결과") +@Getter +public class GetVoteOptionRes { + + @Schema(description = "투표 항목 ID", example = "1") + private Long voteOptionId; + + @Schema(description = "투표 항목", example = "있다") + private String optionText; + + @Schema(description = "투표수", example = "5") + private int voteCount; + + public GetVoteOptionRes(Long voteOptionId, String optionText, int voteCount) { + this.voteOptionId = voteOptionId; + this.optionText = optionText; + this.voteCount = voteCount; + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java new file mode 100644 index 00000000..333103a7 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java @@ -0,0 +1,26 @@ +package devkor.com.teamcback.domain.vote.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +import java.util.List; + +@Schema(description = "투표 목록 조회 결과") +@Getter +public class GetVoteRes { + + @Schema(description = "투표 주제 ID", example = "1") + private Long voteTopicId; + + @Schema(description = "투표 주제", example = "콘센트 유무") + private String topic; + + @Schema(description = "투표 항목 리스트") + private List optionList; + + public GetVoteRes(Long voteTopicId, String topic, List optionList) { + this.voteTopicId = voteTopicId; + this.topic = topic; + this.optionList = optionList; + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/dto/response/SaveVoteRecordRes.java b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/SaveVoteRecordRes.java new file mode 100644 index 00000000..2b08c45d --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/SaveVoteRecordRes.java @@ -0,0 +1,9 @@ +package devkor.com.teamcback.domain.vote.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "투표 내용 저장 완료") +@JsonIgnoreProperties +public class SaveVoteRecordRes { +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java new file mode 100644 index 00000000..9d93e308 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java @@ -0,0 +1,28 @@ +package devkor.com.teamcback.domain.vote.entity; + +import devkor.com.teamcback.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Table(name = "tb_vote_option") +@NoArgsConstructor +public class VoteOption extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long voteTopicId; + + @Column(nullable = false) + private String optionText; + + @Column(nullable = false) + private int voteCount = 0; +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java new file mode 100644 index 00000000..2afa8cd4 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java @@ -0,0 +1,37 @@ +package devkor.com.teamcback.domain.vote.entity; + +import devkor.com.teamcback.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Table(name = "tb_vote_record") +@NoArgsConstructor +public class VoteRecord extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long userId; + + @Column(nullable = false) + private Long placeId; + + @Column(nullable = false) + private Long voteTopicId; + + @Column(nullable = false) + private Long voteOptionId; + + public VoteRecord(Long userId, Long placeId, Long voteTopicId, Long voteOptionId) { + this.userId = userId; + this.placeId = placeId; + this.voteTopicId = voteTopicId; + this.voteOptionId = voteOptionId; + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java new file mode 100644 index 00000000..29c70d1e --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java @@ -0,0 +1,10 @@ +package devkor.com.teamcback.domain.vote.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum VoteStatus { + OPEN, CLOSED; +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteTopic.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteTopic.java new file mode 100644 index 00000000..6d83a926 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteTopic.java @@ -0,0 +1,30 @@ +package devkor.com.teamcback.domain.vote.entity; + +import devkor.com.teamcback.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +@Table(name = "tb_vote_topic") +@NoArgsConstructor +public class VoteTopic extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String topic; + + @Column + private LocalDateTime expiredAt; + + @Column + @Enumerated(EnumType.STRING) + private VoteStatus status; +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteOptionRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteOptionRepository.java new file mode 100644 index 00000000..70d2dc94 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteOptionRepository.java @@ -0,0 +1,10 @@ +package devkor.com.teamcback.domain.vote.repository; + +import devkor.com.teamcback.domain.vote.entity.VoteOption; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface VoteOptionRepository extends JpaRepository { + List findAllByVoteTopicId(Long id); +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java new file mode 100644 index 00000000..1f7767ab --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java @@ -0,0 +1,8 @@ +package devkor.com.teamcback.domain.vote.repository; + +import devkor.com.teamcback.domain.vote.entity.VoteRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VoteRecordRepository extends JpaRepository { + VoteRecord findByUserIdAndVoteTopicIdAndVoteOptionIdAndPlaceId(Long userId, Long voteTopicId, Long voteOptionId, Long placeId); +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteTopicRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteTopicRepository.java new file mode 100644 index 00000000..0f3b34d7 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteTopicRepository.java @@ -0,0 +1,14 @@ +package devkor.com.teamcback.domain.vote.repository; + +import devkor.com.teamcback.domain.vote.entity.VoteStatus; +import devkor.com.teamcback.domain.vote.entity.VoteTopic; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface VoteTopicRepository extends JpaRepository { + @Query("SELECT v FROM VoteTopic v WHERE (:status IS NULL OR v.status = :status)") + List findAllByStatus(@Param("status") VoteStatus status); +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java new file mode 100644 index 00000000..2bda1ee1 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java @@ -0,0 +1,114 @@ +package devkor.com.teamcback.domain.vote.service; + +import devkor.com.teamcback.domain.place.entity.Place; +import devkor.com.teamcback.domain.place.repository.PlaceRepository; +import devkor.com.teamcback.domain.vote.dto.response.GetVoteOptionRes; +import devkor.com.teamcback.domain.vote.dto.response.GetVoteRes; +import devkor.com.teamcback.domain.vote.dto.response.SaveVoteRecordRes; +import devkor.com.teamcback.domain.vote.entity.VoteOption; +import devkor.com.teamcback.domain.vote.entity.VoteRecord; +import devkor.com.teamcback.domain.vote.entity.VoteStatus; +import devkor.com.teamcback.domain.vote.entity.VoteTopic; +import devkor.com.teamcback.domain.vote.repository.VoteOptionRepository; +import devkor.com.teamcback.domain.vote.repository.VoteRecordRepository; +import devkor.com.teamcback.domain.vote.repository.VoteTopicRepository; +import devkor.com.teamcback.global.exception.exception.GlobalException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static devkor.com.teamcback.domain.vote.entity.VoteStatus.CLOSED; +import static devkor.com.teamcback.global.response.ResultCode.*; + +@Service +@RequiredArgsConstructor +public class VoteService { + private final PlaceRepository placeRepository; + private final VoteTopicRepository voteTopicRepository; + private final VoteOptionRepository voteOptionRepository; + private final VoteRecordRepository voteRecordRepository; + + /** + * 투표 리스트 조회 + * @param status 투표 상태 + * @return + */ + public List getVoteList(VoteStatus status) { + List voteResList = new ArrayList<>(); + + List voteTopicList = voteTopicRepository.findAllByStatus(status); + + for(VoteTopic voteTopic : voteTopicList) { + List voteOptionList = voteOptionRepository.findAllByVoteTopicId(voteTopic.getId()) + .stream().map(option -> new GetVoteOptionRes(option.getId(), option.getOptionText(), option.getVoteCount())).toList(); + + voteResList.add(new GetVoteRes(voteTopic.getId(), voteTopic.getTopic(), voteOptionList)); + } + + return voteResList; + } + + /** + * 투표 조회 + * @param voteTopicId 투표 주제 ID + * @return + */ + public GetVoteRes getVote(Long voteTopicId) { + + VoteTopic voteTopic = findVoteTopic(voteTopicId); + + List voteOptionList = voteOptionRepository.findAllByVoteTopicId(voteTopic.getId()) + .stream().map(option -> new GetVoteOptionRes(option.getId(), option.getOptionText(), option.getVoteCount())).toList(); + + + return new GetVoteRes(voteTopic.getId(), voteTopic.getTopic(), voteOptionList); + } + + @Transactional + public SaveVoteRecordRes saveVoteRecord(Long userId, Long voteTopicId, Long voteOptionId, Long placeId) { + if(userId == null) throw new GlobalException(FORBIDDEN); + + VoteOption voteOption = findVoteOption(voteOptionId); + + // 투표 주제의 옵션이 아닌 경우 + if(!Objects.equals(voteOption.getVoteTopicId(), voteTopicId)) throw new GlobalException(INVALID_INPUT); + + // 투표 주제 상태 확인 + VoteTopic voteTopic = findVoteTopic(voteTopicId); + if(voteTopic.getStatus() == CLOSED) throw new GlobalException(CLOSED_VOTE); + + // 투표 장소 확인 + if(placeId != null) { + findPlace(placeId); + } + + // 투표 이력 확인 + VoteRecord voteRecord = voteRecordRepository.findByUserIdAndVoteTopicIdAndVoteOptionIdAndPlaceId(userId, voteTopicId, voteOptionId, placeId); + if(voteRecord == null) { + voteRecordRepository.save(new VoteRecord(userId, voteTopicId, voteOptionId, placeId)); + voteOption.setVoteCount(voteOption.getVoteCount() + 1); + } + else { + voteRecordRepository.deleteById(voteRecord.getId()); + voteOption.setVoteCount(voteOption.getVoteCount() - 1); + } + + return new SaveVoteRecordRes(); + } + + private VoteTopic findVoteTopic(Long voteTopicId) { + return voteTopicRepository.findById(voteTopicId).orElseThrow(() -> new GlobalException(NOT_FOUND_VOTE)); + } + + private VoteOption findVoteOption(Long voteOptionId) { + return voteOptionRepository.findById(voteOptionId).orElseThrow(() -> new GlobalException(NOT_FOUND_VOTE_OPTION)); + } + + private Place findPlace(Long placeId) { + return placeRepository.findById(placeId).orElseThrow(() -> new GlobalException(NOT_FOUND_PLACE)); + } +} diff --git a/src/main/java/devkor/com/teamcback/global/response/ResultCode.java b/src/main/java/devkor/com/teamcback/global/response/ResultCode.java index 880c15ad..fcd79374 100644 --- a/src/main/java/devkor/com/teamcback/global/response/ResultCode.java +++ b/src/main/java/devkor/com/teamcback/global/response/ResultCode.java @@ -74,7 +74,12 @@ public enum ResultCode { OPER_CONDITION_HAS_NO_OPER_TIME(HttpStatus.NOT_FOUND, 10000, "운영 조건이 운영 시간을 가지고 있지 않습니다."), // 고연전 11000번대 - NOT_FOUND_TAG(HttpStatus.NOT_FOUND, 11000, "음식 태그를 찾을 수 없습니다.") + NOT_FOUND_TAG(HttpStatus.NOT_FOUND, 11000, "음식 태그를 찾을 수 없습니다."), + + // 투표 12000번대 + NOT_FOUND_VOTE(HttpStatus.NOT_FOUND, 12000, "투표를 찾을 수 없습니다."), + NOT_FOUND_VOTE_OPTION(HttpStatus.NOT_FOUND, 12001, "투표 항목을 찾을 수 없습니다."), + CLOSED_VOTE(HttpStatus.BAD_REQUEST, 12002, "종료된 투표입니다.") ; private final HttpStatus status; From 02e6b59b87c909058f7bfbada109a1ab058e4083 Mon Sep 17 00:00:00 2001 From: Yeoeun Yang Date: Fri, 5 Sep 2025 17:28:05 +0900 Subject: [PATCH 02/17] =?UTF-8?q?fix:=20"=EC=84=9C=EC=9A=B8=EC=BA=A0?= =?UTF-8?q?=ED=8D=BC=EC=8A=A4"=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/search/dto/response/SearchBuildingDetailRes.java | 2 +- .../teamcback/domain/search/dto/response/SearchBuildingRes.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/devkor/com/teamcback/domain/search/dto/response/SearchBuildingDetailRes.java b/src/main/java/devkor/com/teamcback/domain/search/dto/response/SearchBuildingDetailRes.java index 300def02..ef6ce34e 100644 --- a/src/main/java/devkor/com/teamcback/domain/search/dto/response/SearchBuildingDetailRes.java +++ b/src/main/java/devkor/com/teamcback/domain/search/dto/response/SearchBuildingDetailRes.java @@ -46,7 +46,7 @@ public class SearchBuildingDetailRes { public SearchBuildingDetailRes(List facilities, List types, Building building, String imageUrl, boolean bookmarked) { this.buildingId = building.getId(); - this.name = "고려대학교 서울캠퍼스 " + building.getName(); + this.name = "고려대학교 " + building.getName(); this.address = building.getAddress(); this.latitude = building.getNode().getLatitude(); this.longitude = building.getNode().getLongitude(); diff --git a/src/main/java/devkor/com/teamcback/domain/search/dto/response/SearchBuildingRes.java b/src/main/java/devkor/com/teamcback/domain/search/dto/response/SearchBuildingRes.java index 7b724bd8..8faee75a 100644 --- a/src/main/java/devkor/com/teamcback/domain/search/dto/response/SearchBuildingRes.java +++ b/src/main/java/devkor/com/teamcback/domain/search/dto/response/SearchBuildingRes.java @@ -33,7 +33,7 @@ public class SearchBuildingRes { public SearchBuildingRes(Building building, String imageUrl, List placeTypes) { this.buildingId = building.getId(); - this.name = "고려대학교 서울캠퍼스 " + building.getName(); + this.name = "고려대학교 " + building.getName(); this.imageUrl = imageUrl != null ? imageUrl : building.getImageUrl(); this.detail = building.getDetail(); this.address = building.getAddress(); From 6cf23af4090acf40623c7b329c28c3189e239c59 Mon Sep 17 00:00:00 2001 From: Yeoeun Yang Date: Fri, 5 Sep 2025 17:47:28 +0900 Subject: [PATCH 03/17] =?UTF-8?q?fix:=20=EA=B2=80=EC=83=89=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../teamcback/domain/search/service/SearchService.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/devkor/com/teamcback/domain/search/service/SearchService.java b/src/main/java/devkor/com/teamcback/domain/search/service/SearchService.java index c59c1034..c5796037 100644 --- a/src/main/java/devkor/com/teamcback/domain/search/service/SearchService.java +++ b/src/main/java/devkor/com/teamcback/domain/search/service/SearchService.java @@ -78,13 +78,12 @@ public class SearchService { // TODO: 리필로드 구현 완료되면 REUSABLE_CUP_RETURN 제거하기 private static final List excludedTypes = Arrays.asList(PlaceType.CLASSROOM.getName(), PlaceType.TOILET.getName(), PlaceType.MEN_TOILET.getName(), PlaceType.WOMEN_TOILET.getName(), PlaceType.MEN_HANDICAPPED_TOILET.getName(), - PlaceType.WOMEN_HANDICAPPED_TOILET.getName(), PlaceType.LOCKER.getName(), PlaceType.TRASH_CAN.getName(), - PlaceType.BICYCLE_RACK.getName(), PlaceType.BENCH.getName(), PlaceType.REUSABLE_CUP_RETURN.getName()); + PlaceType.WOMEN_HANDICAPPED_TOILET.getName(), PlaceType.LOCKER.getName(), PlaceType.BICYCLE_RACK.getName(), PlaceType.BENCH.getName()); // 통합 검색 결과에서 "건물명 + 기본편의시설명"의 형태로 제공되어야 하는 편의시설 종류 private static final List outerTagTypes = Arrays.asList(PlaceType.CAFE, PlaceType.CAFETERIA, PlaceType.CONVENIENCE_STORE, - PlaceType.READING_ROOM, PlaceType.STUDY_ROOM, PlaceType.BOOK_RETURN_MACHINE, PlaceType.LOUNGE, PlaceType.WATER_PURIFIER, - PlaceType.VENDING_MACHINE, PlaceType.PRINTER, PlaceType.TUMBLER_WASHER, PlaceType.ONESTOP_AUTO_MACHINE, PlaceType.BANK, + PlaceType.READING_ROOM, PlaceType.STUDY_ROOM, PlaceType.BOOK_RETURN_MACHINE, PlaceType.LOUNGE, PlaceType.WATER_PURIFIER, PlaceType.REUSABLE_CUP_RETURN, + PlaceType.VENDING_MACHINE, PlaceType.PRINTER, PlaceType.TUMBLER_WASHER, PlaceType.ONESTOP_AUTO_MACHINE, PlaceType.BANK, PlaceType.TRASH_CAN, PlaceType.SMOKING_BOOTH, PlaceType.SHOWER_ROOM, PlaceType.GYM, PlaceType.SLEEPING_ROOM, PlaceType.HEALTH_OFFICE, PlaceType.DISABLED_PARKING); // 건물 상세 조회 : 대표 편의시설 종류 @@ -138,7 +137,7 @@ public GlobalSearchListRes globalSearch(String word, Long userId) { } } - logUtil.logSearch(word); +// logUtil.logSearch(word); return new GlobalSearchListRes(orderSequence(scores)); } From 7ca50fc3f3f8ef61783a008a7062eeffc100a5c6 Mon Sep 17 00:00:00 2001 From: Yeoeun Yang Date: Fri, 5 Sep 2025 17:52:21 +0900 Subject: [PATCH 04/17] =?UTF-8?q?fix:=20=EB=A1=9C=EA=B9=85=20=EC=A3=BC?= =?UTF-8?q?=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/teamcback/domain/search/service/SearchService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/devkor/com/teamcback/domain/search/service/SearchService.java b/src/main/java/devkor/com/teamcback/domain/search/service/SearchService.java index c5796037..255afa6e 100644 --- a/src/main/java/devkor/com/teamcback/domain/search/service/SearchService.java +++ b/src/main/java/devkor/com/teamcback/domain/search/service/SearchService.java @@ -75,7 +75,6 @@ public class SearchService { // 통합 검색 결과에 표시하지 않을 편의시설 종류 //TODO: 자전거보관소, 벤치 디자인 요청 후 List에서 제거 - // TODO: 리필로드 구현 완료되면 REUSABLE_CUP_RETURN 제거하기 private static final List excludedTypes = Arrays.asList(PlaceType.CLASSROOM.getName(), PlaceType.TOILET.getName(), PlaceType.MEN_TOILET.getName(), PlaceType.WOMEN_TOILET.getName(), PlaceType.MEN_HANDICAPPED_TOILET.getName(), PlaceType.WOMEN_HANDICAPPED_TOILET.getName(), PlaceType.LOCKER.getName(), PlaceType.BICYCLE_RACK.getName(), PlaceType.BENCH.getName()); @@ -137,7 +136,7 @@ public GlobalSearchListRes globalSearch(String word, Long userId) { } } -// logUtil.logSearch(word); + logUtil.logSearch(word); return new GlobalSearchListRes(orderSequence(scores)); } From ed3264e709de6922b25b44f51996e84a99a165b4 Mon Sep 17 00:00:00 2001 From: yejin Date: Sat, 6 Sep 2025 14:14:15 +0900 Subject: [PATCH 05/17] =?UTF-8?q?Feat:=20=EB=B0=A9=ED=95=99=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B3=A0=EC=97=B0=EC=A0=84=20=EC=97=AC=EB=B6=80=20api=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/SchoolCalendarController.java | 36 +++++++++++++++++++ .../dto/response/GetVacationRes.java | 23 ++++++++++++ .../SchoolCalendar/entity/SchoolCalendar.java | 22 ++++++++++++ .../repository/SchoolCalendarRepository.java | 7 ++++ .../service/SchoolCalendarService.java | 29 +++++++++++++++ .../domain/koyeon/entity/Koyeon.java | 8 +++++ .../domain/koyeon/service/KoyeonService.java | 10 +++++- .../suggestion/service/SuggestionService.java | 1 - .../global/jwt/JwtAuthorizationFilter.java | 1 + .../teamcback/global/response/ResultCode.java | 5 +-- 10 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 src/main/java/devkor/com/teamcback/domain/SchoolCalendar/controller/SchoolCalendarController.java create mode 100644 src/main/java/devkor/com/teamcback/domain/SchoolCalendar/dto/response/GetVacationRes.java create mode 100644 src/main/java/devkor/com/teamcback/domain/SchoolCalendar/entity/SchoolCalendar.java create mode 100644 src/main/java/devkor/com/teamcback/domain/SchoolCalendar/repository/SchoolCalendarRepository.java create mode 100644 src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/controller/SchoolCalendarController.java b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/controller/SchoolCalendarController.java new file mode 100644 index 00000000..93e8a53a --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/controller/SchoolCalendarController.java @@ -0,0 +1,36 @@ +package devkor.com.teamcback.domain.SchoolCalendar.controller; + +import devkor.com.teamcback.domain.SchoolCalendar.dto.response.GetVacationRes; +import devkor.com.teamcback.domain.SchoolCalendar.service.SchoolCalendarService; +import devkor.com.teamcback.global.response.CommonResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/school-calendar") +public class SchoolCalendarController { + private final SchoolCalendarService schoolCalendarService; + + /*** + * 방학 여부 반환 + */ + @GetMapping("/vacation") + @Operation(summary = "방학 여부를 t/f로 반환", description = "방학 여부를 t/f로 반환") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "Not Found", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + public CommonResponse isKoyeon() { + return CommonResponse.success(schoolCalendarService.isVacation()); + } + +} diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/dto/response/GetVacationRes.java b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/dto/response/GetVacationRes.java new file mode 100644 index 00000000..cac5ae9d --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/dto/response/GetVacationRes.java @@ -0,0 +1,23 @@ +package devkor.com.teamcback.domain.SchoolCalendar.dto.response; + +import devkor.com.teamcback.domain.SchoolCalendar.entity.SchoolCalendar; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Schema(description = "방학 여부 응답 dto") +@Getter +public class GetVacationRes { + + @Schema(description = "학교 일정 ID", example = "1") + private Long id; + @Schema(description = "학교 일정 이름", example = "방학") + private String name; + @Schema(description = "일정 여부", example = "true") + private boolean isActive; + + public GetVacationRes(SchoolCalendar schoolCalendar) { + this.id = schoolCalendar.getId(); + this.name = schoolCalendar.getName(); + this.isActive = schoolCalendar.isActive(); + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/entity/SchoolCalendar.java b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/entity/SchoolCalendar.java new file mode 100644 index 00000000..3586353d --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/entity/SchoolCalendar.java @@ -0,0 +1,22 @@ +package devkor.com.teamcback.domain.SchoolCalendar.entity; + +import devkor.com.teamcback.domain.common.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Table(name = "tb_school_calendar") +@NoArgsConstructor +public class SchoolCalendar extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private boolean isActive; +} diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/repository/SchoolCalendarRepository.java b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/repository/SchoolCalendarRepository.java new file mode 100644 index 00000000..45e8f383 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/repository/SchoolCalendarRepository.java @@ -0,0 +1,7 @@ +package devkor.com.teamcback.domain.SchoolCalendar.repository; + +import devkor.com.teamcback.domain.SchoolCalendar.entity.SchoolCalendar; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SchoolCalendarRepository extends JpaRepository { +} diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java new file mode 100644 index 00000000..9f2b66e5 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java @@ -0,0 +1,29 @@ +package devkor.com.teamcback.domain.SchoolCalendar.service; + +import devkor.com.teamcback.domain.SchoolCalendar.dto.response.GetVacationRes; +import devkor.com.teamcback.domain.SchoolCalendar.entity.SchoolCalendar; +import devkor.com.teamcback.domain.SchoolCalendar.repository.SchoolCalendarRepository; +import devkor.com.teamcback.global.exception.exception.GlobalException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import static devkor.com.teamcback.global.response.ResultCode.NOT_FOUND_SCHOOL_CALENDAR; + +@Service +@RequiredArgsConstructor +public class SchoolCalendarService { + private final SchoolCalendarRepository schoolCalendarRepository; + + public GetVacationRes isVacation() { + SchoolCalendar schoolCalendar = findSchoolCalendar(1L); + return new GetVacationRes(schoolCalendar); + } + + /** + * 방학 여부 반환 + */ + private SchoolCalendar findSchoolCalendar(Long id) { + return schoolCalendarRepository.findById(id).orElseThrow(() -> new GlobalException(NOT_FOUND_SCHOOL_CALENDAR)); + } + +} diff --git a/src/main/java/devkor/com/teamcback/domain/koyeon/entity/Koyeon.java b/src/main/java/devkor/com/teamcback/domain/koyeon/entity/Koyeon.java index 58d8da55..be633a83 100644 --- a/src/main/java/devkor/com/teamcback/domain/koyeon/entity/Koyeon.java +++ b/src/main/java/devkor/com/teamcback/domain/koyeon/entity/Koyeon.java @@ -11,6 +11,14 @@ public class Koyeon { @Id @Schema(description = "id", example = "1") private Long id; + @Schema(description = "name", example = "고연전") + private String name; @Schema(description = "isKoyeon", example = "true") private Boolean isKoyeon; + + public Koyeon(Long id, String name, Boolean isKoyeon) { + this.id = id; + this.name = name; + this.isKoyeon = isKoyeon; + } } diff --git a/src/main/java/devkor/com/teamcback/domain/koyeon/service/KoyeonService.java b/src/main/java/devkor/com/teamcback/domain/koyeon/service/KoyeonService.java index 241630c2..f8a3a067 100644 --- a/src/main/java/devkor/com/teamcback/domain/koyeon/service/KoyeonService.java +++ b/src/main/java/devkor/com/teamcback/domain/koyeon/service/KoyeonService.java @@ -1,5 +1,7 @@ package devkor.com.teamcback.domain.koyeon.service; +import devkor.com.teamcback.domain.SchoolCalendar.entity.SchoolCalendar; +import devkor.com.teamcback.domain.SchoolCalendar.repository.SchoolCalendarRepository; import devkor.com.teamcback.domain.koyeon.dto.response.*; import devkor.com.teamcback.domain.koyeon.entity.*; import devkor.com.teamcback.domain.koyeon.repository.*; @@ -24,13 +26,15 @@ public class KoyeonService { private final MenuRepository menuRepository; private final TagMenuRepository tagMenuRepository; private final FreePubNicknameRepository freePubNicknameRepository; + private final SchoolCalendarRepository schoolCalendarRepository; /** * 고연전 여부 확인 */ @Transactional(readOnly = true) public Koyeon isKoyeon() { - return koyeonRepository.findById(1L).orElseThrow(() -> new GlobalException(NOT_FOUND_KOYEON)); + SchoolCalendar schoolCalendar = findSchoolCalendar(); + return new Koyeon(schoolCalendar.getId(), schoolCalendar.getName(), schoolCalendar.isActive()); } /** @@ -126,4 +130,8 @@ private static List orderSequence(Map new GlobalException(NOT_FOUND_PUB)); } + + private SchoolCalendar findSchoolCalendar() { + return schoolCalendarRepository.findById(2L).orElseThrow(() -> new GlobalException(NOT_FOUND_SCHOOL_CALENDAR)); + } } diff --git a/src/main/java/devkor/com/teamcback/domain/suggestion/service/SuggestionService.java b/src/main/java/devkor/com/teamcback/domain/suggestion/service/SuggestionService.java index 10e26768..6ac266c3 100644 --- a/src/main/java/devkor/com/teamcback/domain/suggestion/service/SuggestionService.java +++ b/src/main/java/devkor/com/teamcback/domain/suggestion/service/SuggestionService.java @@ -10,7 +10,6 @@ import devkor.com.teamcback.domain.suggestion.dto.response.GetSuggestionRes; import devkor.com.teamcback.domain.suggestion.dto.response.ModifySuggestionRes; import devkor.com.teamcback.domain.suggestion.entity.Suggestion; -import devkor.com.teamcback.domain.suggestion.entity.SuggestionImage; import devkor.com.teamcback.domain.suggestion.entity.SuggestionType; import devkor.com.teamcback.domain.suggestion.repository.SuggestionRepository; import devkor.com.teamcback.domain.user.entity.User; diff --git a/src/main/java/devkor/com/teamcback/global/jwt/JwtAuthorizationFilter.java b/src/main/java/devkor/com/teamcback/global/jwt/JwtAuthorizationFilter.java index c85697cc..434654cf 100644 --- a/src/main/java/devkor/com/teamcback/global/jwt/JwtAuthorizationFilter.java +++ b/src/main/java/devkor/com/teamcback/global/jwt/JwtAuthorizationFilter.java @@ -32,6 +32,7 @@ public class JwtAuthorizationFilter extends OncePerRequestFilter { private static final List whiteList = List.of( new AntPathRequestMatcher("/api/migration"), + new AntPathRequestMatcher("/api/school-calendar/**"), new AntPathRequestMatcher("/api/koyeon/**"), new AntPathRequestMatcher("/api/routes/**"), new AntPathRequestMatcher("/api/search", HttpMethod.GET.name()), diff --git a/src/main/java/devkor/com/teamcback/global/response/ResultCode.java b/src/main/java/devkor/com/teamcback/global/response/ResultCode.java index 880c15ad..b610885e 100644 --- a/src/main/java/devkor/com/teamcback/global/response/ResultCode.java +++ b/src/main/java/devkor/com/teamcback/global/response/ResultCode.java @@ -73,8 +73,9 @@ public enum ResultCode { // 운영 시간 10000번대 OPER_CONDITION_HAS_NO_OPER_TIME(HttpStatus.NOT_FOUND, 10000, "운영 조건이 운영 시간을 가지고 있지 않습니다."), - // 고연전 11000번대 - NOT_FOUND_TAG(HttpStatus.NOT_FOUND, 11000, "음식 태그를 찾을 수 없습니다.") + // 행사 11000번대 + NOT_FOUND_TAG(HttpStatus.NOT_FOUND, 11000, "음식 태그를 찾을 수 없습니다."), + NOT_FOUND_SCHOOL_CALENDAR(HttpStatus.NOT_FOUND, 11001, "관련 일정을 찾을 수 없습니다.") ; private final HttpStatus status; From f1621dbe9581048bb2009f267979859d0f163eb6 Mon Sep 17 00:00:00 2001 From: yejin Date: Sat, 6 Sep 2025 14:18:00 +0900 Subject: [PATCH 06/17] =?UTF-8?q?Fix:=20=EC=9A=B4=EC=98=81=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/SchoolCalendarService.java | 12 +++++-- .../domain/koyeon/entity/Koyeon.java | 2 ++ .../scheduler/OperatingScheduler.java | 35 +++---------------- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java index 9f2b66e5..6825fb76 100644 --- a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java +++ b/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java @@ -14,16 +14,24 @@ public class SchoolCalendarService { private final SchoolCalendarRepository schoolCalendarRepository; + /** + * 방학 여부 반환 + */ public GetVacationRes isVacation() { SchoolCalendar schoolCalendar = findSchoolCalendar(1L); return new GetVacationRes(schoolCalendar); } + private SchoolCalendar findSchoolCalendar(Long id) { + return schoolCalendarRepository.findById(id).orElseThrow(() -> new GlobalException(NOT_FOUND_SCHOOL_CALENDAR)); + } + /** * 방학 여부 반환 */ - private SchoolCalendar findSchoolCalendar(Long id) { - return schoolCalendarRepository.findById(id).orElseThrow(() -> new GlobalException(NOT_FOUND_SCHOOL_CALENDAR)); + public boolean isVacationTf() { + SchoolCalendar schoolCalendar = schoolCalendarRepository.findById(1L).orElseThrow(() -> new GlobalException(NOT_FOUND_SCHOOL_CALENDAR)); + return schoolCalendar.isActive(); } } diff --git a/src/main/java/devkor/com/teamcback/domain/koyeon/entity/Koyeon.java b/src/main/java/devkor/com/teamcback/domain/koyeon/entity/Koyeon.java index be633a83..685e32e5 100644 --- a/src/main/java/devkor/com/teamcback/domain/koyeon/entity/Koyeon.java +++ b/src/main/java/devkor/com/teamcback/domain/koyeon/entity/Koyeon.java @@ -3,10 +3,12 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.*; import lombok.Getter; +import lombok.NoArgsConstructor; @Entity @Getter @Table(name = "tb_koyeon") +@NoArgsConstructor public class Koyeon { @Id @Schema(description = "id", example = "1") diff --git a/src/main/java/devkor/com/teamcback/domain/operatingtime/scheduler/OperatingScheduler.java b/src/main/java/devkor/com/teamcback/domain/operatingtime/scheduler/OperatingScheduler.java index e1b7a2f5..f5c517e0 100644 --- a/src/main/java/devkor/com/teamcback/domain/operatingtime/scheduler/OperatingScheduler.java +++ b/src/main/java/devkor/com/teamcback/domain/operatingtime/scheduler/OperatingScheduler.java @@ -1,5 +1,6 @@ package devkor.com.teamcback.domain.operatingtime.scheduler; +import devkor.com.teamcback.domain.SchoolCalendar.service.SchoolCalendarService; import devkor.com.teamcback.domain.operatingtime.entity.DayOfWeek; import devkor.com.teamcback.domain.operatingtime.service.HolidayService; import devkor.com.teamcback.domain.operatingtime.service.OperatingService; @@ -21,16 +22,7 @@ public class OperatingScheduler { private final OperatingService operatingService; private final HolidayService holidayService; private final RedisLockUtil redisLockUtil; - - private static final int SUMMER_VACATION_START_MONTH = 6; - private static final int SUMMER_VACATION_START_DAY = 22; - private static final int SUMMER_VACATION_END_MONTH = 9; - private static final int SUMMER_VACATION_END_DAY = 1; - - private static final int WINTER_VACATION_START_MONTH = 12; - private static final int WINTER_VACATION_START_DAY = 21; - private static final int WINTER_VACATION_END_MONTH = 3; - private static final int WINTER_VACATION_END_DAY = 3; + private final SchoolCalendarService schoolCalendarService; private static DayOfWeek dayOfWeek = null; private static Boolean isHoliday = null; @@ -86,7 +78,7 @@ private void setState() { log.info("dayOfWeek: {}", dayOfWeek.toString()); isHoliday = isHoliday(now); // 공휴일 여부 log.info("isHoliday: {}", isHoliday); - isVacation = isVacation(now); // 방학 여부 + isVacation = isVacation(); // 방학 여부 log.info("isVacation: {}", isVacation); isEvenWeek = false; // 토요일 짝수 주 여부 if(dayOfWeek == DayOfWeek.SATURDAY) { // 토요일이면 몇째주 토요일인지 계산 @@ -113,25 +105,8 @@ private boolean isHoliday(LocalDate date) { return holidayService.isHoliday(date); } - private boolean isVacation(LocalDate date) { - int month = date.getMonthValue(); - int day = date.getDayOfMonth(); - - // 여름방학 기간 - if((month == SUMMER_VACATION_START_MONTH && day >= SUMMER_VACATION_START_DAY) || - (month == SUMMER_VACATION_END_MONTH && day <= SUMMER_VACATION_END_DAY) || - (month > SUMMER_VACATION_START_MONTH && month < SUMMER_VACATION_END_MONTH)) { - return true; - } - - // 겨울방학 기간 - if((month == WINTER_VACATION_START_MONTH && day >= WINTER_VACATION_START_DAY) || - (month == WINTER_VACATION_END_MONTH && day <= WINTER_VACATION_END_DAY) || - (month < WINTER_VACATION_END_MONTH)) { - return true; - } - - return false; + private boolean isVacation() { + return schoolCalendarService.isVacationTf(); } private boolean isEvenWeek(LocalDate now) { From 71b27c7f6592fd03c0819a0f22f5f2cce6f23b8f Mon Sep 17 00:00:00 2001 From: yejin Date: Sat, 6 Sep 2025 14:20:03 +0900 Subject: [PATCH 07/17] =?UTF-8?q?Fix:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../teamcback/domain/koyeon/service/KoyeonService.java | 4 ++-- .../operatingtime/scheduler/OperatingScheduler.java | 2 +- .../controller/SchoolCalendarController.java | 6 +++--- .../dto/response/GetVacationRes.java | 4 ++-- .../entity/SchoolCalendar.java | 2 +- .../repository/SchoolCalendarRepository.java | 4 ++-- .../service/SchoolCalendarService.java | 8 ++++---- 7 files changed, 15 insertions(+), 15 deletions(-) rename src/main/java/devkor/com/teamcback/domain/{SchoolCalendar => schoolcalendar}/controller/SchoolCalendarController.java (87%) rename src/main/java/devkor/com/teamcback/domain/{SchoolCalendar => schoolcalendar}/dto/response/GetVacationRes.java (83%) rename src/main/java/devkor/com/teamcback/domain/{SchoolCalendar => schoolcalendar}/entity/SchoolCalendar.java (88%) rename src/main/java/devkor/com/teamcback/domain/{SchoolCalendar => schoolcalendar}/repository/SchoolCalendarRepository.java (56%) rename src/main/java/devkor/com/teamcback/domain/{SchoolCalendar => schoolcalendar}/service/SchoolCalendarService.java (81%) diff --git a/src/main/java/devkor/com/teamcback/domain/koyeon/service/KoyeonService.java b/src/main/java/devkor/com/teamcback/domain/koyeon/service/KoyeonService.java index f8a3a067..46220a11 100644 --- a/src/main/java/devkor/com/teamcback/domain/koyeon/service/KoyeonService.java +++ b/src/main/java/devkor/com/teamcback/domain/koyeon/service/KoyeonService.java @@ -1,7 +1,7 @@ package devkor.com.teamcback.domain.koyeon.service; -import devkor.com.teamcback.domain.SchoolCalendar.entity.SchoolCalendar; -import devkor.com.teamcback.domain.SchoolCalendar.repository.SchoolCalendarRepository; +import devkor.com.teamcback.domain.schoolcalendar.entity.SchoolCalendar; +import devkor.com.teamcback.domain.schoolcalendar.repository.SchoolCalendarRepository; import devkor.com.teamcback.domain.koyeon.dto.response.*; import devkor.com.teamcback.domain.koyeon.entity.*; import devkor.com.teamcback.domain.koyeon.repository.*; diff --git a/src/main/java/devkor/com/teamcback/domain/operatingtime/scheduler/OperatingScheduler.java b/src/main/java/devkor/com/teamcback/domain/operatingtime/scheduler/OperatingScheduler.java index f5c517e0..b5ffead9 100644 --- a/src/main/java/devkor/com/teamcback/domain/operatingtime/scheduler/OperatingScheduler.java +++ b/src/main/java/devkor/com/teamcback/domain/operatingtime/scheduler/OperatingScheduler.java @@ -1,6 +1,6 @@ package devkor.com.teamcback.domain.operatingtime.scheduler; -import devkor.com.teamcback.domain.SchoolCalendar.service.SchoolCalendarService; +import devkor.com.teamcback.domain.schoolcalendar.service.SchoolCalendarService; import devkor.com.teamcback.domain.operatingtime.entity.DayOfWeek; import devkor.com.teamcback.domain.operatingtime.service.HolidayService; import devkor.com.teamcback.domain.operatingtime.service.OperatingService; diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/controller/SchoolCalendarController.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java similarity index 87% rename from src/main/java/devkor/com/teamcback/domain/SchoolCalendar/controller/SchoolCalendarController.java rename to src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java index 93e8a53a..42a96f2d 100644 --- a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/controller/SchoolCalendarController.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java @@ -1,7 +1,7 @@ -package devkor.com.teamcback.domain.SchoolCalendar.controller; +package devkor.com.teamcback.domain.schoolcalendar.controller; -import devkor.com.teamcback.domain.SchoolCalendar.dto.response.GetVacationRes; -import devkor.com.teamcback.domain.SchoolCalendar.service.SchoolCalendarService; +import devkor.com.teamcback.domain.schoolcalendar.dto.response.GetVacationRes; +import devkor.com.teamcback.domain.schoolcalendar.service.SchoolCalendarService; import devkor.com.teamcback.global.response.CommonResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/dto/response/GetVacationRes.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/GetVacationRes.java similarity index 83% rename from src/main/java/devkor/com/teamcback/domain/SchoolCalendar/dto/response/GetVacationRes.java rename to src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/GetVacationRes.java index cac5ae9d..64888076 100644 --- a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/dto/response/GetVacationRes.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/GetVacationRes.java @@ -1,6 +1,6 @@ -package devkor.com.teamcback.domain.SchoolCalendar.dto.response; +package devkor.com.teamcback.domain.schoolcalendar.dto.response; -import devkor.com.teamcback.domain.SchoolCalendar.entity.SchoolCalendar; +import devkor.com.teamcback.domain.schoolcalendar.entity.SchoolCalendar; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/entity/SchoolCalendar.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/entity/SchoolCalendar.java similarity index 88% rename from src/main/java/devkor/com/teamcback/domain/SchoolCalendar/entity/SchoolCalendar.java rename to src/main/java/devkor/com/teamcback/domain/schoolcalendar/entity/SchoolCalendar.java index 3586353d..1b60df50 100644 --- a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/entity/SchoolCalendar.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/entity/SchoolCalendar.java @@ -1,4 +1,4 @@ -package devkor.com.teamcback.domain.SchoolCalendar.entity; +package devkor.com.teamcback.domain.schoolcalendar.entity; import devkor.com.teamcback.domain.common.entity.BaseEntity; import jakarta.persistence.*; diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/repository/SchoolCalendarRepository.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/repository/SchoolCalendarRepository.java similarity index 56% rename from src/main/java/devkor/com/teamcback/domain/SchoolCalendar/repository/SchoolCalendarRepository.java rename to src/main/java/devkor/com/teamcback/domain/schoolcalendar/repository/SchoolCalendarRepository.java index 45e8f383..f0db8e82 100644 --- a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/repository/SchoolCalendarRepository.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/repository/SchoolCalendarRepository.java @@ -1,6 +1,6 @@ -package devkor.com.teamcback.domain.SchoolCalendar.repository; +package devkor.com.teamcback.domain.schoolcalendar.repository; -import devkor.com.teamcback.domain.SchoolCalendar.entity.SchoolCalendar; +import devkor.com.teamcback.domain.schoolcalendar.entity.SchoolCalendar; import org.springframework.data.jpa.repository.JpaRepository; public interface SchoolCalendarRepository extends JpaRepository { diff --git a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java similarity index 81% rename from src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java rename to src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java index 6825fb76..293722a7 100644 --- a/src/main/java/devkor/com/teamcback/domain/SchoolCalendar/service/SchoolCalendarService.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java @@ -1,8 +1,8 @@ -package devkor.com.teamcback.domain.SchoolCalendar.service; +package devkor.com.teamcback.domain.schoolcalendar.service; -import devkor.com.teamcback.domain.SchoolCalendar.dto.response.GetVacationRes; -import devkor.com.teamcback.domain.SchoolCalendar.entity.SchoolCalendar; -import devkor.com.teamcback.domain.SchoolCalendar.repository.SchoolCalendarRepository; +import devkor.com.teamcback.domain.schoolcalendar.dto.response.GetVacationRes; +import devkor.com.teamcback.domain.schoolcalendar.entity.SchoolCalendar; +import devkor.com.teamcback.domain.schoolcalendar.repository.SchoolCalendarRepository; import devkor.com.teamcback.global.exception.exception.GlobalException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; From 84ea5b02a2a1f1b6bd0fef94557beef64133adb7 Mon Sep 17 00:00:00 2001 From: yejin Date: Sat, 6 Sep 2025 14:33:10 +0900 Subject: [PATCH 08/17] =?UTF-8?q?Feat:=20=ED=95=99=EA=B5=90=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=97=AC=EB=B6=80=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AdminSchoolCalendarController.java | 50 +++++++++++++++++++ .../controller/SchoolCalendarController.java | 4 +- ...tionRes.java => GetSchoolCalendarRes.java} | 4 +- .../dto/response/UpdateSchoolCalendarRes.java | 9 ++++ .../schoolcalendar/entity/SchoolCalendar.java | 2 + .../service/SchoolCalendarService.java | 38 ++++++++++++-- 6 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/AdminSchoolCalendarController.java rename src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/{GetVacationRes.java => GetSchoolCalendarRes.java} (87%) create mode 100644 src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/UpdateSchoolCalendarRes.java diff --git a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/AdminSchoolCalendarController.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/AdminSchoolCalendarController.java new file mode 100644 index 00000000..51a13b8c --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/AdminSchoolCalendarController.java @@ -0,0 +1,50 @@ +package devkor.com.teamcback.domain.schoolcalendar.controller; + +import devkor.com.teamcback.domain.schoolcalendar.dto.response.UpdateSchoolCalendarRes; +import devkor.com.teamcback.domain.schoolcalendar.service.SchoolCalendarService; +import devkor.com.teamcback.global.response.CommonResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/admin/school-calendar") +public class AdminSchoolCalendarController { + private final SchoolCalendarService schoolCalendarService; + + /*** + * 방학 여부 수정 + */ + @PutMapping("/vacation") + @Operation(summary = "방학 여부 수정(토글)", description = "방학 여부 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "Not Found", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + public CommonResponse updateVacationActive() { + return CommonResponse.success(schoolCalendarService.updateVacationActive()); + } + + /*** + * 고연전 여부 수정 + */ + @PutMapping("/koyeon") + @Operation(summary = "고연전 여부 수정(토글)", description = "고연전 여부 수정") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "Not Found", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + public CommonResponse updateKoyeonActive() { + return CommonResponse.success(schoolCalendarService.updateKoyeonActive()); + } + +} diff --git a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java index 42a96f2d..58bf851d 100644 --- a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java @@ -1,6 +1,6 @@ package devkor.com.teamcback.domain.schoolcalendar.controller; -import devkor.com.teamcback.domain.schoolcalendar.dto.response.GetVacationRes; +import devkor.com.teamcback.domain.schoolcalendar.dto.response.GetSchoolCalendarRes; import devkor.com.teamcback.domain.schoolcalendar.service.SchoolCalendarService; import devkor.com.teamcback.global.response.CommonResponse; import io.swagger.v3.oas.annotations.Operation; @@ -29,7 +29,7 @@ public class SchoolCalendarController { @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) - public CommonResponse isKoyeon() { + public CommonResponse isVacation() { return CommonResponse.success(schoolCalendarService.isVacation()); } diff --git a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/GetVacationRes.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/GetSchoolCalendarRes.java similarity index 87% rename from src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/GetVacationRes.java rename to src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/GetSchoolCalendarRes.java index 64888076..6e909f58 100644 --- a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/GetVacationRes.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/GetSchoolCalendarRes.java @@ -6,7 +6,7 @@ @Schema(description = "방학 여부 응답 dto") @Getter -public class GetVacationRes { +public class GetSchoolCalendarRes { @Schema(description = "학교 일정 ID", example = "1") private Long id; @@ -15,7 +15,7 @@ public class GetVacationRes { @Schema(description = "일정 여부", example = "true") private boolean isActive; - public GetVacationRes(SchoolCalendar schoolCalendar) { + public GetSchoolCalendarRes(SchoolCalendar schoolCalendar) { this.id = schoolCalendar.getId(); this.name = schoolCalendar.getName(); this.isActive = schoolCalendar.isActive(); diff --git a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/UpdateSchoolCalendarRes.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/UpdateSchoolCalendarRes.java new file mode 100644 index 00000000..39f9494b --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/dto/response/UpdateSchoolCalendarRes.java @@ -0,0 +1,9 @@ +package devkor.com.teamcback.domain.schoolcalendar.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "편의시설 수정 응답 dto") +@JsonIgnoreProperties +public class UpdateSchoolCalendarRes { +} diff --git a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/entity/SchoolCalendar.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/entity/SchoolCalendar.java index 1b60df50..9bd3cdfd 100644 --- a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/entity/SchoolCalendar.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/entity/SchoolCalendar.java @@ -4,6 +4,7 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; @Entity @Getter @@ -17,6 +18,7 @@ public class SchoolCalendar extends BaseEntity { @Column(nullable = false) private String name; + @Setter @Column(nullable = false) private boolean isActive; } diff --git a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java index 293722a7..db3de281 100644 --- a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java @@ -1,9 +1,11 @@ package devkor.com.teamcback.domain.schoolcalendar.service; -import devkor.com.teamcback.domain.schoolcalendar.dto.response.GetVacationRes; +import devkor.com.teamcback.domain.schoolcalendar.dto.response.GetSchoolCalendarRes; +import devkor.com.teamcback.domain.schoolcalendar.dto.response.UpdateSchoolCalendarRes; import devkor.com.teamcback.domain.schoolcalendar.entity.SchoolCalendar; import devkor.com.teamcback.domain.schoolcalendar.repository.SchoolCalendarRepository; import devkor.com.teamcback.global.exception.exception.GlobalException; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -17,13 +19,27 @@ public class SchoolCalendarService { /** * 방학 여부 반환 */ - public GetVacationRes isVacation() { + public GetSchoolCalendarRes isVacation() { SchoolCalendar schoolCalendar = findSchoolCalendar(1L); - return new GetVacationRes(schoolCalendar); + return new GetSchoolCalendarRes(schoolCalendar); } - private SchoolCalendar findSchoolCalendar(Long id) { - return schoolCalendarRepository.findById(id).orElseThrow(() -> new GlobalException(NOT_FOUND_SCHOOL_CALENDAR)); + /** + * 방학 여부 수정(토글) + */ + @Transactional + public UpdateSchoolCalendarRes updateVacationActive() { + updateSchoolCalendarActive(1L); + return new UpdateSchoolCalendarRes(); + } + + /** + * 고연전 여부 수정(토글) + */ + @Transactional + public UpdateSchoolCalendarRes updateKoyeonActive() { + updateSchoolCalendarActive(2L); + return new UpdateSchoolCalendarRes(); } /** @@ -34,4 +50,16 @@ public boolean isVacationTf() { return schoolCalendar.isActive(); } + /** + * 학교 일정 진행 여부 수정 + */ + private void updateSchoolCalendarActive(long id) { + SchoolCalendar schoolCalendar = findSchoolCalendar(id); + schoolCalendar.setActive(!schoolCalendar.isActive()); // 수정 + } + + private SchoolCalendar findSchoolCalendar(Long id) { + return schoolCalendarRepository.findById(id).orElseThrow(() -> new GlobalException(NOT_FOUND_SCHOOL_CALENDAR)); + } + } From 6d2f0c295de136bc39e89a186b3dde19bc56afae Mon Sep 17 00:00:00 2001 From: yejin Date: Sat, 6 Sep 2025 14:53:04 +0900 Subject: [PATCH 09/17] =?UTF-8?q?Feat:=20=EA=B3=A0=EC=97=B0=EC=A0=84=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=20=EC=A1=B0=ED=9A=8C=20api=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/koyeon/controller/KoyeonController.java | 1 + .../controller/SchoolCalendarController.java | 14 ++++++++++++++ .../service/SchoolCalendarService.java | 10 ++++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/devkor/com/teamcback/domain/koyeon/controller/KoyeonController.java b/src/main/java/devkor/com/teamcback/domain/koyeon/controller/KoyeonController.java index 9314072b..dc0a1912 100644 --- a/src/main/java/devkor/com/teamcback/domain/koyeon/controller/KoyeonController.java +++ b/src/main/java/devkor/com/teamcback/domain/koyeon/controller/KoyeonController.java @@ -23,6 +23,7 @@ public class KoyeonController { /*** * 고연전 여부 반환 + * TODO: 프론트 api 변경 후 삭제 필요 */ @GetMapping("") @Operation(summary = "고연전 시즌 여부를 t/f로 반환", description = "고연전 시즌 여부를 t/f로 반환") diff --git a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java index 58bf851d..409913bd 100644 --- a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/controller/SchoolCalendarController.java @@ -33,4 +33,18 @@ public CommonResponse isVacation() { return CommonResponse.success(schoolCalendarService.isVacation()); } + /*** + * 고연전 여부 반환 + */ + @GetMapping("/koyeon") + @Operation(summary = "고연전 여부를 t/f로 반환", description = "고연전 여부를 t/f로 반환") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "Not Found", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + public CommonResponse isKoyeon() { + return CommonResponse.success(schoolCalendarService.isKoyeon()); + } + } diff --git a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java index db3de281..87a0e04d 100644 --- a/src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java +++ b/src/main/java/devkor/com/teamcback/domain/schoolcalendar/service/SchoolCalendarService.java @@ -20,8 +20,14 @@ public class SchoolCalendarService { * 방학 여부 반환 */ public GetSchoolCalendarRes isVacation() { - SchoolCalendar schoolCalendar = findSchoolCalendar(1L); - return new GetSchoolCalendarRes(schoolCalendar); + return new GetSchoolCalendarRes(findSchoolCalendar(1L)); + } + + /** + * 고연전 여부 반환 + */ + public GetSchoolCalendarRes isKoyeon() { + return new GetSchoolCalendarRes(findSchoolCalendar(2L)); } /** From 238f0c3394a55a9b97ef21f3166ed102e5fc3128 Mon Sep 17 00:00:00 2001 From: yejin Date: Sat, 6 Sep 2025 17:30:17 +0900 Subject: [PATCH 10/17] =?UTF-8?q?Feat:=20=ED=88=AC=ED=91=9C=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vote/controller/VoteController.java | 31 ++++-- .../vote/dto/request/SaveVoteRecordReq.java | 19 ++++ .../domain/vote/dto/response/GetVoteRes.java | 7 +- .../domain/vote/entity/VoteOption.java | 2 - .../domain/vote/entity/VoteRecord.java | 2 +- .../domain/vote/entity/VoteStatus.java | 5 +- .../vote/repository/VoteRecordRepository.java | 12 +- .../domain/vote/service/VoteService.java | 105 ++++++++++++++---- .../global/aop/UpdateScoreAspect.java | 9 ++ 9 files changed, 154 insertions(+), 38 deletions(-) create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/dto/request/SaveVoteRecordReq.java diff --git a/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java b/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java index c0badab2..236ef570 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java @@ -1,5 +1,6 @@ package devkor.com.teamcback.domain.vote.controller; +import devkor.com.teamcback.domain.vote.dto.request.SaveVoteRecordReq; import devkor.com.teamcback.domain.vote.dto.response.GetVoteRes; import devkor.com.teamcback.domain.vote.dto.response.SaveVoteRecordRes; import devkor.com.teamcback.domain.vote.entity.VoteStatus; @@ -59,11 +60,28 @@ public CommonResponse getVote( return CommonResponse.success(voteService.getVote(voteTopicId)); } + /** + * 장소별 투표 정보 조회 + * @param placeId 장소 ID + */ + @Operation(summary = "투표 정보 조회", description = "투표 정보 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "Not Found", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + @GetMapping("/{voteTopicId}/places/{placeId}") + public CommonResponse getVoteByPlace( + @Parameter(description = "투표 주제 ID", example = "1") + @PathVariable(name = "voteTopicId") Long voteTopicId, + @Parameter(description = "장소 ID", example = "1") + @PathVariable(name = "placeId") Long placeId) { + return CommonResponse.success(voteService.getVoteByPlace(voteTopicId, placeId)); + } + /** * 투표 기록 저장 * @param userDetail 사용자 정보 - * @param voteTopicId 투표 주제 ID - * @param voteOptionId 투표 옵션 ID */ @Operation(summary = "투표 저장", description = "투표 저장") @ApiResponses(value = { @@ -75,15 +93,10 @@ public CommonResponse getVote( public CommonResponse saveVoteRecord( @Parameter(description = "사용자정보") @AuthenticationPrincipal UserDetailsImpl userDetail, - @Parameter(name = "voteTopicId", description = "투표 주제 ID", example = "1") - @PathVariable(name = "voteTopicId") Long voteTopicId, - @Parameter(name = "voteOptionId", description = "투표 옵션 ID", example = "1") - @RequestParam(name = "voteOptionId") Long voteOptionId, - @Parameter(name = "placeId", description = "투표 장소 ID", example = "1") - @RequestParam(name = "placeId", required = false) Long placeId + @RequestBody SaveVoteRecordReq req ) { Long userId = userDetail == null ? null : userDetail.getUser().getUserId(); - return CommonResponse.success(voteService.saveVoteRecord(userId, voteTopicId, voteOptionId, placeId)); + return CommonResponse.success(voteService.saveVoteRecord(userId, req)); } } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/dto/request/SaveVoteRecordReq.java b/src/main/java/devkor/com/teamcback/domain/vote/dto/request/SaveVoteRecordReq.java new file mode 100644 index 00000000..dcfb2e02 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/dto/request/SaveVoteRecordReq.java @@ -0,0 +1,19 @@ +package devkor.com.teamcback.domain.vote.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Schema(description = "장소에 대한 투표 정보") +@Getter +@AllArgsConstructor +public class SaveVoteRecordReq { + @Schema(description = "장소 id", example = "5") + private Long placeId; + + @Schema(description = "투표 주제 id", example = "1") + private Long voteTopicId; + + @Schema(description = "투표 항목 id", example = "1") + private Long voteOptionId; +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java index 333103a7..651c7e75 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java @@ -1,5 +1,6 @@ package devkor.com.teamcback.domain.vote.dto.response; +import devkor.com.teamcback.domain.vote.entity.VoteStatus; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @@ -15,12 +16,16 @@ public class GetVoteRes { @Schema(description = "투표 주제", example = "콘센트 유무") private String topic; + @Schema(description = "투표 상태", example = "종료") + private String status; + @Schema(description = "투표 항목 리스트") private List optionList; - public GetVoteRes(Long voteTopicId, String topic, List optionList) { + public GetVoteRes(Long voteTopicId, String topic, String status, List optionList) { this.voteTopicId = voteTopicId; this.topic = topic; + this.status = status; this.optionList = optionList; } } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java index 9d93e308..8944bf18 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java @@ -23,6 +23,4 @@ public class VoteOption extends BaseEntity { @Column(nullable = false) private String optionText; - @Column(nullable = false) - private int voteCount = 0; } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java index 2afa8cd4..53bf88f3 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java @@ -25,7 +25,7 @@ public class VoteRecord extends BaseEntity { @Column(nullable = false) private Long voteTopicId; - @Column(nullable = false) + @Column private Long voteOptionId; public VoteRecord(Long userId, Long placeId, Long voteTopicId, Long voteOptionId) { diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java index 29c70d1e..390dea24 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java @@ -6,5 +6,8 @@ @Getter @AllArgsConstructor public enum VoteStatus { - OPEN, CLOSED; + OPEN("진행 중"), + CLOSED("종료"); + + private final String name; } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java index 1f7767ab..7b169594 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java @@ -3,6 +3,16 @@ import devkor.com.teamcback.domain.vote.entity.VoteRecord; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface VoteRecordRepository extends JpaRepository { - VoteRecord findByUserIdAndVoteTopicIdAndVoteOptionIdAndPlaceId(Long userId, Long voteTopicId, Long voteOptionId, Long placeId); + VoteRecord findByUserIdAndVoteTopicIdAndPlaceId(Long userId, Long voteTopicId, Long placeId); + + List findAllByVoteTopicIdAndPlaceId(Long voteTopicId, Long placeId); + + int countByVoteTopicIdAndVoteOptionId(Long voteTopicId, Long voteOptionId); + + int countByVoteTopicIdAndVoteOptionIdAndPlaceId(Long voteTopicId, Long voteOptionId, Long placeId); + + boolean existsByUserIdAndPlaceIdAndVoteTopicId(Long userId, Long placeId, Long voteTopicId); } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java index 2bda1ee1..28ae1632 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java @@ -2,6 +2,7 @@ import devkor.com.teamcback.domain.place.entity.Place; import devkor.com.teamcback.domain.place.repository.PlaceRepository; +import devkor.com.teamcback.domain.vote.dto.request.SaveVoteRecordReq; import devkor.com.teamcback.domain.vote.dto.response.GetVoteOptionRes; import devkor.com.teamcback.domain.vote.dto.response.GetVoteRes; import devkor.com.teamcback.domain.vote.dto.response.SaveVoteRecordRes; @@ -12,14 +13,13 @@ import devkor.com.teamcback.domain.vote.repository.VoteOptionRepository; import devkor.com.teamcback.domain.vote.repository.VoteRecordRepository; import devkor.com.teamcback.domain.vote.repository.VoteTopicRepository; +import devkor.com.teamcback.global.annotation.UpdateScore; import devkor.com.teamcback.global.exception.exception.GlobalException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; +import java.util.*; import static devkor.com.teamcback.domain.vote.entity.VoteStatus.CLOSED; import static devkor.com.teamcback.global.response.ResultCode.*; @@ -43,10 +43,15 @@ public List getVoteList(VoteStatus status) { List voteTopicList = voteTopicRepository.findAllByStatus(status); for(VoteTopic voteTopic : voteTopicList) { - List voteOptionList = voteOptionRepository.findAllByVoteTopicId(voteTopic.getId()) - .stream().map(option -> new GetVoteOptionRes(option.getId(), option.getOptionText(), option.getVoteCount())).toList(); + List voteOptionList = voteOptionRepository.findAllByVoteTopicId(voteTopic.getId()); - voteResList.add(new GetVoteRes(voteTopic.getId(), voteTopic.getTopic(), voteOptionList)); + List voteOptionResList = new ArrayList<>(); + for(VoteOption voteOption : voteOptionList) { + int voteCount = voteRecordRepository.countByVoteTopicIdAndVoteOptionId(voteTopic.getId(), voteOption.getId()); + voteOptionResList.add(new GetVoteOptionRes(voteOption.getId(), voteOption.getOptionText(), voteCount)); + } + + voteResList.add(new GetVoteRes(voteTopic.getId(), voteTopic.getTopic(), voteTopic.getStatus().getName(), voteOptionResList)); } return voteResList; @@ -61,40 +66,94 @@ public GetVoteRes getVote(Long voteTopicId) { VoteTopic voteTopic = findVoteTopic(voteTopicId); - List voteOptionList = voteOptionRepository.findAllByVoteTopicId(voteTopic.getId()) - .stream().map(option -> new GetVoteOptionRes(option.getId(), option.getOptionText(), option.getVoteCount())).toList(); + List voteOptionList = voteOptionRepository.findAllByVoteTopicId(voteTopic.getId()); + List voteOptionResList = new ArrayList<>(); + for(VoteOption voteOption : voteOptionList) { + int voteCount = voteRecordRepository.countByVoteTopicIdAndVoteOptionId(voteTopicId, voteOption.getId()); + voteOptionResList.add(new GetVoteOptionRes(voteOption.getId(), voteOption.getOptionText(), voteCount)); + } - return new GetVoteRes(voteTopic.getId(), voteTopic.getTopic(), voteOptionList); + return new GetVoteRes(voteTopic.getId(), voteTopic.getTopic(), voteTopic.getStatus().getName(), voteOptionResList); } + /** + * 투표 조회 + * @param placeId 장소 ID + * @return + */ + public GetVoteRes getVoteByPlace(Long voteTopicId, Long placeId) { + + VoteTopic voteTopic = findVoteTopic(voteTopicId); + + List voteOptionList = voteOptionRepository.findAllByVoteTopicId(voteTopic.getId()); + + List voteOptionResList = new ArrayList<>(); + for(VoteOption voteOption : voteOptionList) { + int voteCount = voteRecordRepository.countByVoteTopicIdAndVoteOptionIdAndPlaceId(voteTopicId, voteOption.getId(), placeId); + voteOptionResList.add(new GetVoteOptionRes(voteOption.getId(), voteOption.getOptionText(), voteCount)); + } + return new GetVoteRes(voteTopic.getId(), voteTopic.getTopic(), voteTopic.getStatus().getName(), voteOptionResList); + } + + + /** + * 투표 저장 + */ + @UpdateScore(addScore = 5) @Transactional - public SaveVoteRecordRes saveVoteRecord(Long userId, Long voteTopicId, Long voteOptionId, Long placeId) { + public SaveVoteRecordRes saveVoteRecord(Long userId, SaveVoteRecordReq req) { if(userId == null) throw new GlobalException(FORBIDDEN); - VoteOption voteOption = findVoteOption(voteOptionId); + VoteOption voteOption = findVoteOption(req.getVoteOptionId()); // 투표 주제의 옵션이 아닌 경우 - if(!Objects.equals(voteOption.getVoteTopicId(), voteTopicId)) throw new GlobalException(INVALID_INPUT); + if(!Objects.equals(voteOption.getVoteTopicId(), req.getVoteTopicId())) throw new GlobalException(INVALID_INPUT); // 투표 주제 상태 확인 - VoteTopic voteTopic = findVoteTopic(voteTopicId); + VoteTopic voteTopic = findVoteTopic(req.getVoteTopicId()); if(voteTopic.getStatus() == CLOSED) throw new GlobalException(CLOSED_VOTE); // 투표 장소 확인 - if(placeId != null) { - findPlace(placeId); - } + Place place = findPlace(req.getPlaceId()); // 투표 이력 확인 - VoteRecord voteRecord = voteRecordRepository.findByUserIdAndVoteTopicIdAndVoteOptionIdAndPlaceId(userId, voteTopicId, voteOptionId, placeId); - if(voteRecord == null) { - voteRecordRepository.save(new VoteRecord(userId, voteTopicId, voteOptionId, placeId)); - voteOption.setVoteCount(voteOption.getVoteCount() + 1); + VoteRecord voteRecord = voteRecordRepository.findByUserIdAndVoteTopicIdAndPlaceId(userId, req.getVoteTopicId(), req.getPlaceId()); + if(voteRecord == null) { // 다른 거 선택한 경우 새로 저장 + voteRecordRepository.save(new VoteRecord(userId, req.getVoteTopicId(), req.getVoteOptionId(), req.getPlaceId())); } - else { - voteRecordRepository.deleteById(voteRecord.getId()); - voteOption.setVoteCount(voteOption.getVoteCount() - 1); + else if(!voteRecord.getVoteOptionId().equals(voteOption.getId())){ // 다른 걸 투표한 경우 + voteRecord.setVoteOptionId(req.getVoteOptionId()); + } + else { // 같은 걸 투표한 경우 -> 토글로 취소됨 + voteRecord.setVoteOptionId(null); // 기존 투표 옵션을 null로 설정 (기록 남기기) + } + + // 투표 종료 검사 로직 + List optionList = voteOptionRepository.findAllByVoteTopicId(req.getVoteTopicId()); + if(optionList.isEmpty()) return new SaveVoteRecordRes(); + + Integer minCount = null; + Integer maxCount = null; + Long maxOption = null; + for(VoteOption option : optionList) { + int count = voteRecordRepository.countByVoteTopicIdAndVoteOptionIdAndPlaceId(req.getVoteTopicId(), option.getId(), req.getPlaceId()); + if(minCount == null) { + minCount = maxCount = count; + maxOption = option.getId(); + } + + else if(count < minCount) { + minCount = count; + } + else if(count > maxCount) { + maxCount = count; + maxOption = option.getId(); + } + } + + if(maxCount >= 5 && maxCount >= minCount * 2) { + // voteTopic.setStatus(CLOSED); } return new SaveVoteRecordRes(); diff --git a/src/main/java/devkor/com/teamcback/global/aop/UpdateScoreAspect.java b/src/main/java/devkor/com/teamcback/global/aop/UpdateScoreAspect.java index 6acb9dfb..869d97da 100644 --- a/src/main/java/devkor/com/teamcback/global/aop/UpdateScoreAspect.java +++ b/src/main/java/devkor/com/teamcback/global/aop/UpdateScoreAspect.java @@ -5,6 +5,8 @@ import devkor.com.teamcback.domain.user.entity.Level; import devkor.com.teamcback.domain.user.entity.User; import devkor.com.teamcback.domain.user.repository.UserRepository; +import devkor.com.teamcback.domain.vote.dto.request.SaveVoteRecordReq; +import devkor.com.teamcback.domain.vote.repository.VoteRecordRepository; import devkor.com.teamcback.global.annotation.UpdateScore; import devkor.com.teamcback.global.exception.exception.GlobalException; import lombok.RequiredArgsConstructor; @@ -28,6 +30,7 @@ public class UpdateScoreAspect { private final UserRepository userRepository; private final UserBookmarkLogRepository userBookmarkLogRepository; + private final VoteRecordRepository voteRecordRepository; @Around("@annotation(updateScore)") public Object updateScore(ProceedingJoinPoint joinPoint, UpdateScore updateScore) throws Throwable { @@ -88,6 +91,12 @@ private boolean checkUpdatable(User user, Object[] args) { return false; } } + + if (arg instanceof SaveVoteRecordReq req) { + if (voteRecordRepository.existsByUserIdAndPlaceIdAndVoteTopicId(user.getUserId(), req.getPlaceId(), req.getVoteTopicId())) { + return false; + } + } } return true; } From 396745c6a652e8aa4bb0f8bcc297e22751cba50b Mon Sep 17 00:00:00 2001 From: yejin Date: Sat, 6 Sep 2025 23:04:02 +0900 Subject: [PATCH 11/17] =?UTF-8?q?Feat:=20=ED=88=AC=ED=91=9C=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vote/controller/VoteController.java | 41 +----- .../vote/dto/request/SaveVoteRecordReq.java | 10 +- .../vote/dto/response/GetVoteOptionRes.java | 2 + .../domain/vote/dto/response/GetVoteRes.java | 19 ++- .../teamcback/domain/vote/entity/Vote.java | 34 +++++ .../domain/vote/entity/VoteOption.java | 2 +- .../domain/vote/entity/VoteRecord.java | 12 +- .../domain/vote/entity/VoteTopic.java | 11 +- .../vote/repository/CustomVoteRepository.java | 10 ++ .../repository/CustomVoteRepositoryImpl.java | 48 +++++++ .../vote/repository/VoteOptionRepository.java | 3 +- .../vote/repository/VoteRecordRepository.java | 10 +- .../vote/repository/VoteRepository.java | 10 ++ .../vote/repository/VoteTopicRepository.java | 6 - .../domain/vote/service/VoteService.java | 130 ++++++------------ .../global/aop/UpdateScoreAspect.java | 2 +- .../teamcback/global/response/ResultCode.java | 5 +- 17 files changed, 178 insertions(+), 177 deletions(-) create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRepository.java diff --git a/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java b/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java index 236ef570..34c4ce9a 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java @@ -3,7 +3,6 @@ import devkor.com.teamcback.domain.vote.dto.request.SaveVoteRecordReq; import devkor.com.teamcback.domain.vote.dto.response.GetVoteRes; import devkor.com.teamcback.domain.vote.dto.response.SaveVoteRecordRes; -import devkor.com.teamcback.domain.vote.entity.VoteStatus; import devkor.com.teamcback.domain.vote.service.VoteService; import devkor.com.teamcback.global.response.CommonResponse; import devkor.com.teamcback.global.security.UserDetailsImpl; @@ -17,49 +16,12 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping("/api/votes") public class VoteController { private final VoteService voteService; - /** - * 투표 리스트 조회 - * @param status 조회할 투표 상태 - */ - @Operation(summary = "투표 리스트 조회", description = "투표 리스트 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), - @ApiResponse(responseCode = "404", description = "Not Found", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - }) - @GetMapping("") - public CommonResponse> getVoteList( - @Parameter(name = "status", description = "투표 상태", example = "OPEN") - @RequestParam(name = "status", required = false) VoteStatus status) { - return CommonResponse.success(voteService.getVoteList(status)); - } - - - /** - * 투표 정보 조회 - * @param voteTopicId 투표 주제 ID - */ - @Operation(summary = "투표 정보 조회", description = "투표 정보 조회") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), - @ApiResponse(responseCode = "404", description = "Not Found", - content = @Content(schema = @Schema(implementation = CommonResponse.class))), - }) - @GetMapping("/{voteTopicId}") - public CommonResponse getVote( - @Parameter(description = "투표 주제 ID", example = "1") - @PathVariable(name = "voteTopicId") Long voteTopicId) { - return CommonResponse.success(voteService.getVote(voteTopicId)); - } - /** * 장소별 투표 정보 조회 * @param placeId 장소 ID @@ -89,10 +51,11 @@ public CommonResponse getVoteByPlace( @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) - @PostMapping("/{voteTopicId}/records") + @PostMapping("") public CommonResponse saveVoteRecord( @Parameter(description = "사용자정보") @AuthenticationPrincipal UserDetailsImpl userDetail, + @Parameter(description = "투표 저장") @RequestBody SaveVoteRecordReq req ) { Long userId = userDetail == null ? null : userDetail.getUser().getUserId(); diff --git a/src/main/java/devkor/com/teamcback/domain/vote/dto/request/SaveVoteRecordReq.java b/src/main/java/devkor/com/teamcback/domain/vote/dto/request/SaveVoteRecordReq.java index dcfb2e02..50b4d4c8 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/dto/request/SaveVoteRecordReq.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/dto/request/SaveVoteRecordReq.java @@ -4,16 +4,12 @@ import lombok.AllArgsConstructor; import lombok.Getter; -@Schema(description = "장소에 대한 투표 정보") @Getter @AllArgsConstructor public class SaveVoteRecordReq { - @Schema(description = "장소 id", example = "5") - private Long placeId; + @Schema(description = "투표 id", example = "1") + private Long voteId; - @Schema(description = "투표 주제 id", example = "1") - private Long voteTopicId; - - @Schema(description = "투표 항목 id", example = "1") + @Schema(description = "투표 옵션 id", example = "1") private Long voteOptionId; } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteOptionRes.java b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteOptionRes.java index b6e415b6..91156681 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteOptionRes.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteOptionRes.java @@ -1,5 +1,6 @@ package devkor.com.teamcback.domain.vote.dto.response; +import com.querydsl.core.annotations.QueryProjection; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @@ -16,6 +17,7 @@ public class GetVoteOptionRes { @Schema(description = "투표수", example = "5") private int voteCount; + @QueryProjection public GetVoteOptionRes(Long voteOptionId, String optionText, int voteCount) { this.voteOptionId = voteOptionId; this.optionText = optionText; diff --git a/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java index 651c7e75..335547db 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/GetVoteRes.java @@ -1,6 +1,5 @@ package devkor.com.teamcback.domain.vote.dto.response; -import devkor.com.teamcback.domain.vote.entity.VoteStatus; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; @@ -10,22 +9,34 @@ @Getter public class GetVoteRes { + @Schema(description = "투표 ID", example = "1") + private Long voteId; + + @Schema(description = "투표 상태", example = "종료") + private String status; + @Schema(description = "투표 주제 ID", example = "1") private Long voteTopicId; @Schema(description = "투표 주제", example = "콘센트 유무") private String topic; - @Schema(description = "투표 상태", example = "종료") - private String status; + @Schema(description = "장소 ID", example = "1") + private Long placeId; + + @Schema(description = "장소명", example = "101호") + private String placeName; @Schema(description = "투표 항목 리스트") private List optionList; - public GetVoteRes(Long voteTopicId, String topic, String status, List optionList) { + public GetVoteRes(Long voteId, String status, Long voteTopicId, String topic, Long placeId, String placeName, List optionList) { + this.voteId = voteId; this.voteTopicId = voteTopicId; this.topic = topic; this.status = status; + this.placeId = placeId; + this.placeName = placeName; this.optionList = optionList; } } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java new file mode 100644 index 00000000..ef1c8aa2 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java @@ -0,0 +1,34 @@ +package devkor.com.teamcback.domain.vote.entity; + +import devkor.com.teamcback.domain.common.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Getter +@Setter +@Table(name = "tb_vote") +@NoArgsConstructor +public class Vote extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long voteTopicId; + + @Column + private Long placeId; + + @Column + @Enumerated(EnumType.STRING) + private VoteStatus status; + + public Vote(Long voteTopicId, Long placeId, VoteStatus status) { + this.voteTopicId = voteTopicId; + this.placeId = placeId; + this.status = status; + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java index 8944bf18..e4f5cb7c 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteOption.java @@ -1,6 +1,6 @@ package devkor.com.teamcback.domain.vote.entity; -import devkor.com.teamcback.domain.common.BaseEntity; +import devkor.com.teamcback.domain.common.entity.BaseEntity; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java index 53bf88f3..21b033fa 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteRecord.java @@ -1,6 +1,6 @@ package devkor.com.teamcback.domain.vote.entity; -import devkor.com.teamcback.domain.common.BaseEntity; +import devkor.com.teamcback.domain.common.entity.BaseEntity; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; @@ -20,18 +20,14 @@ public class VoteRecord extends BaseEntity { private Long userId; @Column(nullable = false) - private Long placeId; - - @Column(nullable = false) - private Long voteTopicId; + private Long voteId; @Column private Long voteOptionId; - public VoteRecord(Long userId, Long placeId, Long voteTopicId, Long voteOptionId) { + public VoteRecord(Long userId, Long voteId, Long voteOptionId) { this.userId = userId; - this.placeId = placeId; - this.voteTopicId = voteTopicId; + this.voteId = voteId; this.voteOptionId = voteOptionId; } } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteTopic.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteTopic.java index 6d83a926..1d1484a2 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteTopic.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteTopic.java @@ -1,13 +1,11 @@ package devkor.com.teamcback.domain.vote.entity; -import devkor.com.teamcback.domain.common.BaseEntity; +import devkor.com.teamcback.domain.common.entity.BaseEntity; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import java.time.LocalDateTime; - @Entity @Getter @Setter @@ -20,11 +18,4 @@ public class VoteTopic extends BaseEntity { @Column(nullable = false) private String topic; - - @Column - private LocalDateTime expiredAt; - - @Column - @Enumerated(EnumType.STRING) - private VoteStatus status; } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java new file mode 100644 index 00000000..e08fce34 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java @@ -0,0 +1,10 @@ +package devkor.com.teamcback.domain.vote.repository; + + +import devkor.com.teamcback.domain.vote.dto.response.GetVoteOptionRes; + +import java.util.List; + +public interface CustomVoteRepository { + List getVoteOptionsByPlaceByVoteTopicIdAndPlaceId(Long voteTopicId, Long placeId); +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java new file mode 100644 index 00000000..fda3b3ed --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java @@ -0,0 +1,48 @@ +package devkor.com.teamcback.domain.vote.repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; + +import static devkor.com.teamcback.domain.vote.entity.QVoteOption.voteOption; +import static devkor.com.teamcback.domain.vote.entity.QVoteRecord.voteRecord; +import static devkor.com.teamcback.domain.vote.entity.QVote.vote; + +import devkor.com.teamcback.domain.vote.dto.response.GetVoteOptionRes; +import devkor.com.teamcback.domain.vote.dto.response.QGetVoteOptionRes; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class CustomVoteRepositoryImpl implements CustomVoteRepository{ + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List getVoteOptionsByPlaceByVoteTopicIdAndPlaceId(Long voteTopicId, Long placeId) { + return jpaQueryFactory + .select( + new QGetVoteOptionRes( + voteOption.id, + voteOption.optionText, + voteRecord.id.count().intValue() + ) + ) + .from(vote) + .join(voteOption) + .on(vote.voteTopicId.eq(voteOption.voteTopicId)) + .leftJoin(voteRecord) + .on(voteRecord.voteId.eq(vote.id) + .and(voteRecord.voteOptionId.eq(voteOption.id))) + .where( + vote.voteTopicId.eq(voteTopicId) + .and(vote.placeId.eq(placeId)) + ) + .groupBy(voteOption.id, voteOption.optionText) + .orderBy( + voteOption.id.asc() + ) + .fetch(); + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteOptionRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteOptionRepository.java index 70d2dc94..6429357d 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteOptionRepository.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteOptionRepository.java @@ -5,6 +5,5 @@ import java.util.List; -public interface VoteOptionRepository extends JpaRepository { - List findAllByVoteTopicId(Long id); +public interface VoteOptionRepository extends JpaRepository, CustomVoteRepository { } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java index 7b169594..86ed6f8b 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRecordRepository.java @@ -6,13 +6,7 @@ import java.util.List; public interface VoteRecordRepository extends JpaRepository { - VoteRecord findByUserIdAndVoteTopicIdAndPlaceId(Long userId, Long voteTopicId, Long placeId); + VoteRecord findByUserIdAndVoteId(Long userId, Long voteId); - List findAllByVoteTopicIdAndPlaceId(Long voteTopicId, Long placeId); - - int countByVoteTopicIdAndVoteOptionId(Long voteTopicId, Long voteOptionId); - - int countByVoteTopicIdAndVoteOptionIdAndPlaceId(Long voteTopicId, Long voteOptionId, Long placeId); - - boolean existsByUserIdAndPlaceIdAndVoteTopicId(Long userId, Long placeId, Long voteTopicId); + boolean existsByUserIdAndVoteId(Long userId, Long voteId); } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRepository.java new file mode 100644 index 00000000..5df55f60 --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRepository.java @@ -0,0 +1,10 @@ +package devkor.com.teamcback.domain.vote.repository; + +import devkor.com.teamcback.domain.vote.entity.Vote; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface VoteRepository extends JpaRepository { + Optional findByVoteTopicIdAndPlaceId(Long voteTopicId, Long placeId); +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteTopicRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteTopicRepository.java index 0f3b34d7..aa60a2ba 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteTopicRepository.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteTopicRepository.java @@ -1,14 +1,8 @@ package devkor.com.teamcback.domain.vote.repository; -import devkor.com.teamcback.domain.vote.entity.VoteStatus; import devkor.com.teamcback.domain.vote.entity.VoteTopic; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import java.util.List; public interface VoteTopicRepository extends JpaRepository { - @Query("SELECT v FROM VoteTopic v WHERE (:status IS NULL OR v.status = :status)") - List findAllByStatus(@Param("status") VoteStatus status); } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java index 28ae1632..7a8ea466 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java @@ -6,12 +6,10 @@ import devkor.com.teamcback.domain.vote.dto.response.GetVoteOptionRes; import devkor.com.teamcback.domain.vote.dto.response.GetVoteRes; import devkor.com.teamcback.domain.vote.dto.response.SaveVoteRecordRes; -import devkor.com.teamcback.domain.vote.entity.VoteOption; -import devkor.com.teamcback.domain.vote.entity.VoteRecord; -import devkor.com.teamcback.domain.vote.entity.VoteStatus; -import devkor.com.teamcback.domain.vote.entity.VoteTopic; +import devkor.com.teamcback.domain.vote.entity.*; import devkor.com.teamcback.domain.vote.repository.VoteOptionRepository; import devkor.com.teamcback.domain.vote.repository.VoteRecordRepository; +import devkor.com.teamcback.domain.vote.repository.VoteRepository; import devkor.com.teamcback.domain.vote.repository.VoteTopicRepository; import devkor.com.teamcback.global.annotation.UpdateScore; import devkor.com.teamcback.global.exception.exception.GlobalException; @@ -31,69 +29,29 @@ public class VoteService { private final VoteTopicRepository voteTopicRepository; private final VoteOptionRepository voteOptionRepository; private final VoteRecordRepository voteRecordRepository; + private final VoteRepository voteRepository; /** - * 투표 리스트 조회 - * @param status 투표 상태 - * @return - */ - public List getVoteList(VoteStatus status) { - List voteResList = new ArrayList<>(); - - List voteTopicList = voteTopicRepository.findAllByStatus(status); - - for(VoteTopic voteTopic : voteTopicList) { - List voteOptionList = voteOptionRepository.findAllByVoteTopicId(voteTopic.getId()); - - List voteOptionResList = new ArrayList<>(); - for(VoteOption voteOption : voteOptionList) { - int voteCount = voteRecordRepository.countByVoteTopicIdAndVoteOptionId(voteTopic.getId(), voteOption.getId()); - voteOptionResList.add(new GetVoteOptionRes(voteOption.getId(), voteOption.getOptionText(), voteCount)); - } - - voteResList.add(new GetVoteRes(voteTopic.getId(), voteTopic.getTopic(), voteTopic.getStatus().getName(), voteOptionResList)); - } - - return voteResList; - } - - /** - * 투표 조회 + * 장소별 투표 조회 * @param voteTopicId 투표 주제 ID - * @return - */ - public GetVoteRes getVote(Long voteTopicId) { - - VoteTopic voteTopic = findVoteTopic(voteTopicId); - - List voteOptionList = voteOptionRepository.findAllByVoteTopicId(voteTopic.getId()); - - List voteOptionResList = new ArrayList<>(); - for(VoteOption voteOption : voteOptionList) { - int voteCount = voteRecordRepository.countByVoteTopicIdAndVoteOptionId(voteTopicId, voteOption.getId()); - voteOptionResList.add(new GetVoteOptionRes(voteOption.getId(), voteOption.getOptionText(), voteCount)); - } - - return new GetVoteRes(voteTopic.getId(), voteTopic.getTopic(), voteTopic.getStatus().getName(), voteOptionResList); - } - - /** - * 투표 조회 * @param placeId 장소 ID * @return */ public GetVoteRes getVoteByPlace(Long voteTopicId, Long placeId) { + // 투표 주제 VoteTopic voteTopic = findVoteTopic(voteTopicId); - List voteOptionList = voteOptionRepository.findAllByVoteTopicId(voteTopic.getId()); + // 투표 + Vote vote = findVote(voteTopicId, placeId); - List voteOptionResList = new ArrayList<>(); - for(VoteOption voteOption : voteOptionList) { - int voteCount = voteRecordRepository.countByVoteTopicIdAndVoteOptionIdAndPlaceId(voteTopicId, voteOption.getId(), placeId); - voteOptionResList.add(new GetVoteOptionRes(voteOption.getId(), voteOption.getOptionText(), voteCount)); - } - return new GetVoteRes(voteTopic.getId(), voteTopic.getTopic(), voteTopic.getStatus().getName(), voteOptionResList); + // 장소 + Place place = findPlace(placeId); + + // 투표 항목, 현황 + List voteOptionList = voteOptionRepository.getVoteOptionsByPlaceByVoteTopicIdAndPlaceId(voteTopic.getId(), placeId); + + return new GetVoteRes(vote.getId(), vote.getStatus().getName(), voteTopic.getId(), voteTopic.getTopic(), place.getId(), place.getName(), voteOptionList); } @@ -105,22 +63,22 @@ public GetVoteRes getVoteByPlace(Long voteTopicId, Long placeId) { public SaveVoteRecordRes saveVoteRecord(Long userId, SaveVoteRecordReq req) { if(userId == null) throw new GlobalException(FORBIDDEN); + // 투표 + Vote vote = findVote(req.getVoteId()); + + // 투표한 옵션 VoteOption voteOption = findVoteOption(req.getVoteOptionId()); // 투표 주제의 옵션이 아닌 경우 - if(!Objects.equals(voteOption.getVoteTopicId(), req.getVoteTopicId())) throw new GlobalException(INVALID_INPUT); + if(!Objects.equals(vote.getVoteTopicId(), voteOption.getVoteTopicId())) throw new GlobalException(INVALID_INPUT); - // 투표 주제 상태 확인 - VoteTopic voteTopic = findVoteTopic(req.getVoteTopicId()); - if(voteTopic.getStatus() == CLOSED) throw new GlobalException(CLOSED_VOTE); - - // 투표 장소 확인 - Place place = findPlace(req.getPlaceId()); + // 투표 상태 확인 + if(vote.getStatus() == CLOSED) throw new GlobalException(CLOSED_VOTE); // 투표 이력 확인 - VoteRecord voteRecord = voteRecordRepository.findByUserIdAndVoteTopicIdAndPlaceId(userId, req.getVoteTopicId(), req.getPlaceId()); + VoteRecord voteRecord = voteRecordRepository.findByUserIdAndVoteId(userId, vote.getId()); if(voteRecord == null) { // 다른 거 선택한 경우 새로 저장 - voteRecordRepository.save(new VoteRecord(userId, req.getVoteTopicId(), req.getVoteOptionId(), req.getPlaceId())); + voteRecordRepository.save(new VoteRecord(userId, vote.getId(), req.getVoteOptionId())); } else if(!voteRecord.getVoteOptionId().equals(voteOption.getId())){ // 다른 걸 투표한 경우 voteRecord.setVoteOptionId(req.getVoteOptionId()); @@ -129,38 +87,32 @@ else if(!voteRecord.getVoteOptionId().equals(voteOption.getId())){ // 다른 걸 voteRecord.setVoteOptionId(null); // 기존 투표 옵션을 null로 설정 (기록 남기기) } - // 투표 종료 검사 로직 - List optionList = voteOptionRepository.findAllByVoteTopicId(req.getVoteTopicId()); - if(optionList.isEmpty()) return new SaveVoteRecordRes(); - - Integer minCount = null; - Integer maxCount = null; - Long maxOption = null; - for(VoteOption option : optionList) { - int count = voteRecordRepository.countByVoteTopicIdAndVoteOptionIdAndPlaceId(req.getVoteTopicId(), option.getId(), req.getPlaceId()); - if(minCount == null) { - minCount = maxCount = count; - maxOption = option.getId(); - } + // 투표 항목, 현황 + List voteOptionList = voteOptionRepository.getVoteOptionsByPlaceByVoteTopicIdAndPlaceId(voteOption.getId(), vote.getPlaceId()) + .stream().sorted(Comparator.comparing(GetVoteOptionRes::getVoteCount)).toList(); - else if(count < minCount) { - minCount = count; - } - else if(count > maxCount) { - maxCount = count; - maxOption = option.getId(); + if(!voteOptionList.isEmpty()) { + int maxCount = voteOptionList.get(voteOptionList.size() - 1).getVoteCount(); + int minCount = voteOptionList.get(0).getVoteCount(); + if(minCount >= 5 && maxCount >= minCount * 2) { + vote.setStatus(CLOSED); + // TODO: 투표 결과 반영 } } - if(maxCount >= 5 && maxCount >= minCount * 2) { - // voteTopic.setStatus(CLOSED); - } - return new SaveVoteRecordRes(); } + private Vote findVote(Long voteTopicId, Long placeId) { + return voteRepository.findByVoteTopicIdAndPlaceId(voteTopicId, placeId).orElseThrow(() -> new GlobalException(NOT_FOUND_VOTE)); + } + + private Vote findVote(Long voteId) { + return voteRepository.findById(voteId).orElseThrow(() -> new GlobalException(NOT_FOUND_VOTE)); + } + private VoteTopic findVoteTopic(Long voteTopicId) { - return voteTopicRepository.findById(voteTopicId).orElseThrow(() -> new GlobalException(NOT_FOUND_VOTE)); + return voteTopicRepository.findById(voteTopicId).orElseThrow(() -> new GlobalException(NOT_FOUND_VOTE_TOPIC)); } private VoteOption findVoteOption(Long voteOptionId) { diff --git a/src/main/java/devkor/com/teamcback/global/aop/UpdateScoreAspect.java b/src/main/java/devkor/com/teamcback/global/aop/UpdateScoreAspect.java index 869d97da..9eb9c9d0 100644 --- a/src/main/java/devkor/com/teamcback/global/aop/UpdateScoreAspect.java +++ b/src/main/java/devkor/com/teamcback/global/aop/UpdateScoreAspect.java @@ -93,7 +93,7 @@ private boolean checkUpdatable(User user, Object[] args) { } if (arg instanceof SaveVoteRecordReq req) { - if (voteRecordRepository.existsByUserIdAndPlaceIdAndVoteTopicId(user.getUserId(), req.getPlaceId(), req.getVoteTopicId())) { + if (voteRecordRepository.existsByUserIdAndVoteId(user.getUserId(), req.getVoteId())) { return false; } } diff --git a/src/main/java/devkor/com/teamcback/global/response/ResultCode.java b/src/main/java/devkor/com/teamcback/global/response/ResultCode.java index 262a3e72..bfe4899f 100644 --- a/src/main/java/devkor/com/teamcback/global/response/ResultCode.java +++ b/src/main/java/devkor/com/teamcback/global/response/ResultCode.java @@ -79,8 +79,9 @@ public enum ResultCode { // 투표 12000번대 NOT_FOUND_VOTE(HttpStatus.NOT_FOUND, 12000, "투표를 찾을 수 없습니다."), - NOT_FOUND_VOTE_OPTION(HttpStatus.NOT_FOUND, 12001, "투표 항목을 찾을 수 없습니다."), - CLOSED_VOTE(HttpStatus.BAD_REQUEST, 12002, "종료된 투표입니다.") + NOT_FOUND_VOTE_TOPIC(HttpStatus.NOT_FOUND, 12001, "투표 주제를 찾을 수 없습니다."), + NOT_FOUND_VOTE_OPTION(HttpStatus.NOT_FOUND, 12002, "투표 항목을 찾을 수 없습니다."), + CLOSED_VOTE(HttpStatus.BAD_REQUEST, 12003, "종료된 투표입니다.") ; private final HttpStatus status; From 9c07d382adbbcfe981090b098d8f3fca550dd43c Mon Sep 17 00:00:00 2001 From: yejin Date: Sat, 6 Sep 2025 23:14:48 +0900 Subject: [PATCH 12/17] =?UTF-8?q?Feat:=20=ED=88=AC=ED=91=9C=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/teamcback/domain/vote/service/VoteService.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java index 7a8ea466..4d557333 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java @@ -80,7 +80,7 @@ public SaveVoteRecordRes saveVoteRecord(Long userId, SaveVoteRecordReq req) { if(voteRecord == null) { // 다른 거 선택한 경우 새로 저장 voteRecordRepository.save(new VoteRecord(userId, vote.getId(), req.getVoteOptionId())); } - else if(!voteRecord.getVoteOptionId().equals(voteOption.getId())){ // 다른 걸 투표한 경우 + else if(!Objects.equals(voteRecord.getVoteOptionId(), voteOption.getId())){ // 다른 걸 투표한 경우 voteRecord.setVoteOptionId(req.getVoteOptionId()); } else { // 같은 걸 투표한 경우 -> 토글로 취소됨 @@ -91,12 +91,17 @@ else if(!voteRecord.getVoteOptionId().equals(voteOption.getId())){ // 다른 걸 List voteOptionList = voteOptionRepository.getVoteOptionsByPlaceByVoteTopicIdAndPlaceId(voteOption.getId(), vote.getPlaceId()) .stream().sorted(Comparator.comparing(GetVoteOptionRes::getVoteCount)).toList(); + // 투표 종료 판단 if(!voteOptionList.isEmpty()) { int maxCount = voteOptionList.get(voteOptionList.size() - 1).getVoteCount(); int minCount = voteOptionList.get(0).getVoteCount(); if(minCount >= 5 && maxCount >= minCount * 2) { vote.setStatus(CLOSED); - // TODO: 투표 결과 반영 + // TODO: 투표 결과 반영 로직 검토(확정된 정보와 투표로 정해진 정보를 구별 불가능함...) + if(vote.getVoteTopicId() == 1L) { // 콘센트 투표인 경우 + Place place = findPlace(vote.getPlaceId()); + place.setPlugAvailability(voteOptionList.get(voteOptionList.size() - 1).getVoteOptionId() == 1); + } } } From 7bb11eb353882ecf7e5a6cbe01d302aa1cf7ba19 Mon Sep 17 00:00:00 2001 From: yejin Date: Sun, 7 Sep 2025 08:51:58 +0900 Subject: [PATCH 13/17] =?UTF-8?q?Feat:=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vote/controller/AdminVoteController.java | 39 +++++++++++++++++++ .../vote/controller/VoteController.java | 2 +- .../dto/response/ChangeVoteStatusRes.java | 9 +++++ .../teamcback/domain/vote/entity/Vote.java | 14 +++++++ .../domain/vote/service/VoteService.java | 37 ++++++++---------- 5 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java create mode 100644 src/main/java/devkor/com/teamcback/domain/vote/dto/response/ChangeVoteStatusRes.java diff --git a/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java b/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java new file mode 100644 index 00000000..215368ae --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java @@ -0,0 +1,39 @@ +package devkor.com.teamcback.domain.vote.controller; + +import devkor.com.teamcback.domain.vote.dto.response.ChangeVoteStatusRes; +import devkor.com.teamcback.domain.vote.service.VoteService; +import devkor.com.teamcback.global.response.CommonResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/admin/votes") +public class AdminVoteController { + private final VoteService voteService; + + /** + * 투표 상태 변경 + */ + @Operation(summary = "투표 상태 변경", description = "투표 상태 변경") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "Not Found", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + @GetMapping("/{voteId}") + public CommonResponse changeVoteStatus( + @Parameter(description = "투표 ID", example = "1") + @PathVariable(name = "voteId") Long voteId) { + return CommonResponse.success(voteService.changeVoteStatus(voteId)); + } +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java b/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java index 34c4ce9a..856b6a4b 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/controller/VoteController.java @@ -32,7 +32,7 @@ public class VoteController { @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) - @GetMapping("/{voteTopicId}/places/{placeId}") + @GetMapping("/topics/{voteTopicId}/places/{placeId}") public CommonResponse getVoteByPlace( @Parameter(description = "투표 주제 ID", example = "1") @PathVariable(name = "voteTopicId") Long voteTopicId, diff --git a/src/main/java/devkor/com/teamcback/domain/vote/dto/response/ChangeVoteStatusRes.java b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/ChangeVoteStatusRes.java new file mode 100644 index 00000000..59d39c8d --- /dev/null +++ b/src/main/java/devkor/com/teamcback/domain/vote/dto/response/ChangeVoteStatusRes.java @@ -0,0 +1,9 @@ +package devkor.com.teamcback.domain.vote.dto.response; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "투표 종료 완료") +@JsonIgnoreProperties +public class ChangeVoteStatusRes { +} diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java index ef1c8aa2..81e4c738 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java @@ -1,11 +1,16 @@ package devkor.com.teamcback.domain.vote.entity; import devkor.com.teamcback.domain.common.entity.BaseEntity; +import devkor.com.teamcback.global.exception.exception.GlobalException; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import static devkor.com.teamcback.domain.vote.entity.VoteStatus.CLOSED; +import static devkor.com.teamcback.domain.vote.entity.VoteStatus.OPEN; +import static devkor.com.teamcback.global.response.ResultCode.CLOSED_VOTE; + @Entity @Getter @Setter @@ -31,4 +36,13 @@ public Vote(Long voteTopicId, Long placeId, VoteStatus status) { this.placeId = placeId; this.status = status; } + + public void checkStatus() { + if(this.status == CLOSED) throw new GlobalException(CLOSED_VOTE); + } + + public void changeStatus() { + if(this.status == CLOSED) this.status = OPEN; + else this.status = CLOSED; + } } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java index 4d557333..3ffbec22 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java @@ -3,6 +3,7 @@ import devkor.com.teamcback.domain.place.entity.Place; import devkor.com.teamcback.domain.place.repository.PlaceRepository; import devkor.com.teamcback.domain.vote.dto.request.SaveVoteRecordReq; +import devkor.com.teamcback.domain.vote.dto.response.ChangeVoteStatusRes; import devkor.com.teamcback.domain.vote.dto.response.GetVoteOptionRes; import devkor.com.teamcback.domain.vote.dto.response.GetVoteRes; import devkor.com.teamcback.domain.vote.dto.response.SaveVoteRecordRes; @@ -37,6 +38,7 @@ public class VoteService { * @param placeId 장소 ID * @return */ + @Transactional(readOnly = true) public GetVoteRes getVoteByPlace(Long voteTopicId, Long placeId) { // 투표 주제 @@ -58,7 +60,7 @@ public GetVoteRes getVoteByPlace(Long voteTopicId, Long placeId) { /** * 투표 저장 */ - @UpdateScore(addScore = 5) + @UpdateScore(addScore = 2) @Transactional public SaveVoteRecordRes saveVoteRecord(Long userId, SaveVoteRecordReq req) { if(userId == null) throw new GlobalException(FORBIDDEN); @@ -73,7 +75,7 @@ public SaveVoteRecordRes saveVoteRecord(Long userId, SaveVoteRecordReq req) { if(!Objects.equals(vote.getVoteTopicId(), voteOption.getVoteTopicId())) throw new GlobalException(INVALID_INPUT); // 투표 상태 확인 - if(vote.getStatus() == CLOSED) throw new GlobalException(CLOSED_VOTE); + vote.checkStatus(); // 투표 이력 확인 VoteRecord voteRecord = voteRecordRepository.findByUserIdAndVoteId(userId, vote.getId()); @@ -87,27 +89,22 @@ else if(!Objects.equals(voteRecord.getVoteOptionId(), voteOption.getId())){ // voteRecord.setVoteOptionId(null); // 기존 투표 옵션을 null로 설정 (기록 남기기) } - // 투표 항목, 현황 - List voteOptionList = voteOptionRepository.getVoteOptionsByPlaceByVoteTopicIdAndPlaceId(voteOption.getId(), vote.getPlaceId()) - .stream().sorted(Comparator.comparing(GetVoteOptionRes::getVoteCount)).toList(); - - // 투표 종료 판단 - if(!voteOptionList.isEmpty()) { - int maxCount = voteOptionList.get(voteOptionList.size() - 1).getVoteCount(); - int minCount = voteOptionList.get(0).getVoteCount(); - if(minCount >= 5 && maxCount >= minCount * 2) { - vote.setStatus(CLOSED); - // TODO: 투표 결과 반영 로직 검토(확정된 정보와 투표로 정해진 정보를 구별 불가능함...) - if(vote.getVoteTopicId() == 1L) { // 콘센트 투표인 경우 - Place place = findPlace(vote.getPlaceId()); - place.setPlugAvailability(voteOptionList.get(voteOptionList.size() - 1).getVoteOptionId() == 1); - } - } - } - return new SaveVoteRecordRes(); } + /** + * 투표 상태 변경(토글) + */ + public ChangeVoteStatusRes changeVoteStatus(Long voteId) { + // 투표 + Vote vote = findVote(voteId); + + // 상태 변경 + vote.changeStatus(); + + return new ChangeVoteStatusRes(); + } + private Vote findVote(Long voteTopicId, Long placeId) { return voteRepository.findByVoteTopicIdAndPlaceId(voteTopicId, placeId).orElseThrow(() -> new GlobalException(NOT_FOUND_VOTE)); } From 8d7b3e0b845ceb2ae45960d1072c8ce4f02f4638 Mon Sep 17 00:00:00 2001 From: yejin Date: Sun, 7 Sep 2025 08:52:51 +0900 Subject: [PATCH 14/17] =?UTF-8?q?Chore:=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/devkor/com/teamcback/domain/vote/entity/Vote.java | 6 ------ .../com/teamcback/domain/vote/service/VoteService.java | 2 -- 2 files changed, 8 deletions(-) diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java index 81e4c738..2f23cbd1 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java @@ -31,12 +31,6 @@ public class Vote extends BaseEntity { @Enumerated(EnumType.STRING) private VoteStatus status; - public Vote(Long voteTopicId, Long placeId, VoteStatus status) { - this.voteTopicId = voteTopicId; - this.placeId = placeId; - this.status = status; - } - public void checkStatus() { if(this.status == CLOSED) throw new GlobalException(CLOSED_VOTE); } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java index 3ffbec22..fe7f7304 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java @@ -20,7 +20,6 @@ import java.util.*; -import static devkor.com.teamcback.domain.vote.entity.VoteStatus.CLOSED; import static devkor.com.teamcback.global.response.ResultCode.*; @Service @@ -36,7 +35,6 @@ public class VoteService { * 장소별 투표 조회 * @param voteTopicId 투표 주제 ID * @param placeId 장소 ID - * @return */ @Transactional(readOnly = true) public GetVoteRes getVoteByPlace(Long voteTopicId, Long placeId) { From e3dffaf2c8e2cf411c33f2a7e6eff63358930b23 Mon Sep 17 00:00:00 2001 From: yejin Date: Tue, 9 Sep 2025 21:54:49 +0900 Subject: [PATCH 15/17] =?UTF-8?q?Feat:=20=ED=88=AC=ED=91=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9A=A9=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vote/controller/AdminVoteController.java | 38 +++++++++--- .../teamcback/domain/vote/entity/Vote.java | 6 +- .../domain/vote/entity/VoteStatus.java | 3 +- .../vote/repository/CustomVoteRepository.java | 6 ++ .../repository/CustomVoteRepositoryImpl.java | 59 +++++++++++++++++++ .../vote/repository/VoteRepository.java | 7 +++ .../domain/vote/service/VoteService.java | 31 +++++++++- 7 files changed, 135 insertions(+), 15 deletions(-) diff --git a/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java b/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java index 215368ae..090473ef 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java @@ -1,6 +1,8 @@ package devkor.com.teamcback.domain.vote.controller; import devkor.com.teamcback.domain.vote.dto.response.ChangeVoteStatusRes; +import devkor.com.teamcback.domain.vote.dto.response.GetVoteRes; +import devkor.com.teamcback.domain.vote.entity.VoteStatus; import devkor.com.teamcback.domain.vote.service.VoteService; import devkor.com.teamcback.global.response.CommonResponse; import io.swagger.v3.oas.annotations.Operation; @@ -10,10 +12,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequiredArgsConstructor @@ -30,10 +31,33 @@ public class AdminVoteController { @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) - @GetMapping("/{voteId}") + @PatchMapping("/{voteId}") public CommonResponse changeVoteStatus( @Parameter(description = "투표 ID", example = "1") - @PathVariable(name = "voteId") Long voteId) { - return CommonResponse.success(voteService.changeVoteStatus(voteId)); + @PathVariable(name = "voteId") Long voteId, + @Parameter(description = "투표 상태", example = "REFLECTED") + @RequestParam(name = "status") VoteStatus status) { + return CommonResponse.success(voteService.changeVoteStatus(voteId, status)); + } + + /** + * 투표 조회 + */ + @Operation(summary = "투표 조회", description = "투표 조회") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."), + @ApiResponse(responseCode = "404", description = "Not Found", + content = @Content(schema = @Schema(implementation = CommonResponse.class))), + }) + @GetMapping() + public CommonResponse> getVoteList( + @Parameter(description = "투표 상태", example = "REFLECTED") + @RequestParam(name = "status", required = false) VoteStatus status, + @Parameter(description = "마지막 조회 투표 ID", example = "0") + @RequestParam(name = "lastVoteId", defaultValue = "0", required = false) Long lastVoteId, + @Parameter(description = "조회 size", example = "20") + @RequestParam(name = "size", defaultValue = "20", required = false) int size + ) { + return CommonResponse.success(voteService.getVoteList(status, lastVoteId, size)); } } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java index 2f23cbd1..c4e62dd0 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java @@ -8,7 +8,6 @@ import lombok.Setter; import static devkor.com.teamcback.domain.vote.entity.VoteStatus.CLOSED; -import static devkor.com.teamcback.domain.vote.entity.VoteStatus.OPEN; import static devkor.com.teamcback.global.response.ResultCode.CLOSED_VOTE; @Entity @@ -35,8 +34,7 @@ public void checkStatus() { if(this.status == CLOSED) throw new GlobalException(CLOSED_VOTE); } - public void changeStatus() { - if(this.status == CLOSED) this.status = OPEN; - else this.status = CLOSED; + public void changeStatus(VoteStatus status) { + this.status = status; } } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java index 390dea24..bc9b6565 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java @@ -7,7 +7,8 @@ @AllArgsConstructor public enum VoteStatus { OPEN("진행 중"), - CLOSED("종료"); + CLOSED("종료"), + REFLECTED("결과 반영됨"); private final String name; } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java index e08fce34..2e53479d 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java @@ -2,9 +2,15 @@ import devkor.com.teamcback.domain.vote.dto.response.GetVoteOptionRes; +import devkor.com.teamcback.domain.vote.entity.Vote; +import devkor.com.teamcback.domain.vote.entity.VoteStatus; import java.util.List; public interface CustomVoteRepository { List getVoteOptionsByPlaceByVoteTopicIdAndPlaceId(Long voteTopicId, Long placeId); + + List getVoteByStatusWithPage(VoteStatus status, Long voteTopicId, int size); + + List getVoteOptionByVoteId(Long voteId); } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java index fda3b3ed..003362c7 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java @@ -1,13 +1,20 @@ package devkor.com.teamcback.domain.vote.repository; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; +import static devkor.com.teamcback.domain.place.entity.QPlace.place; import static devkor.com.teamcback.domain.vote.entity.QVoteOption.voteOption; import static devkor.com.teamcback.domain.vote.entity.QVoteRecord.voteRecord; import static devkor.com.teamcback.domain.vote.entity.QVote.vote; +import devkor.com.teamcback.domain.place.entity.Place; +import devkor.com.teamcback.domain.place.entity.PlaceType; import devkor.com.teamcback.domain.vote.dto.response.GetVoteOptionRes; import devkor.com.teamcback.domain.vote.dto.response.QGetVoteOptionRes; +import devkor.com.teamcback.domain.vote.entity.Vote; +import devkor.com.teamcback.domain.vote.entity.VoteStatus; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -45,4 +52,56 @@ public List getVoteOptionsByPlaceByVoteTopicIdAndPlaceId(Long ) .fetch(); } + + @Override + public List getVoteByStatusWithPage(VoteStatus status, Long lastVoteId, int size) { + BooleanBuilder builder = new BooleanBuilder(); + builder.and(gtVoteCursor(lastVoteId)); + + if (status != null) { + builder.and(vote.status.eq(status)); + } + + return jpaQueryFactory + .selectFrom(vote) + .where( + gtVoteCursor(lastVoteId).and(builder) + ) + .orderBy( + vote.id.asc() + ) + .limit(size) + .fetch(); + } + + @Override + public List getVoteOptionByVoteId(Long voteId) { + return jpaQueryFactory + .select( + new QGetVoteOptionRes( + voteOption.id, + voteOption.optionText, + voteRecord.id.count().intValue() + ) + ) + .from(vote) + .join(voteOption) + .on(vote.voteTopicId.eq(voteOption.voteTopicId)) + .leftJoin(voteRecord) + .on(voteRecord.voteId.eq(vote.id) + .and(voteRecord.voteOptionId.eq(voteOption.id))) + .where( + vote.id.eq(voteId) + ) + .groupBy(voteOption.id, voteOption.optionText) + .orderBy( + voteOption.id.asc() + ) + .fetch(); + } + + private BooleanExpression gtVoteCursor(Long lastVoteId) { + if (lastVoteId== null) return null; + return vote.id.gt(lastVoteId); + } } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRepository.java index 5df55f60..09e14f53 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRepository.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/VoteRepository.java @@ -1,10 +1,17 @@ package devkor.com.teamcback.domain.vote.repository; import devkor.com.teamcback.domain.vote.entity.Vote; +import devkor.com.teamcback.domain.vote.entity.VoteStatus; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; public interface VoteRepository extends JpaRepository { Optional findByVoteTopicIdAndPlaceId(Long voteTopicId, Long placeId); + + @Query("SELECT v FROM Vote v WHERE v.status IS NULL OR v.status = :status") + List findAllByStatus(@Param("status")VoteStatus status); } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java index fe7f7304..8bdad85c 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java @@ -91,14 +91,39 @@ else if(!Objects.equals(voteRecord.getVoteOptionId(), voteOption.getId())){ // } /** - * 투표 상태 변경(토글) + * 투표 리스트 조회 */ - public ChangeVoteStatusRes changeVoteStatus(Long voteId) { + @Transactional(readOnly = true) + public List getVoteList(VoteStatus status, Long lastVoteId, int size) { + List resList = new ArrayList<>(); + + List voteList = voteOptionRepository.getVoteByStatusWithPage(status, lastVoteId, size); + + for(Vote vote : voteList) { + // 투표 주제 + VoteTopic voteTopic = findVoteTopic(vote.getVoteTopicId()); + + // 투표 장소 + Place place = findPlace(vote.getPlaceId()); + + // 투표 항목, 현황 + List voteOptionList = voteOptionRepository.getVoteOptionByVoteId(vote.getId()); + + resList.add(new GetVoteRes(vote.getId(), vote.getStatus().toString(), voteTopic.getId(), voteTopic.getTopic(), place.getId(), place.getName(), voteOptionList)); + } + + return resList; + } + + /** + * 투표 상태 변경 + */ + public ChangeVoteStatusRes changeVoteStatus(Long voteId, VoteStatus status) { // 투표 Vote vote = findVote(voteId); // 상태 변경 - vote.changeStatus(); + vote.changeStatus(status); return new ChangeVoteStatusRes(); } From cedfce9733695e715cb23dc9b0cb5263ea207484 Mon Sep 17 00:00:00 2001 From: yejin Date: Tue, 9 Sep 2025 22:00:56 +0900 Subject: [PATCH 16/17] =?UTF-8?q?Feat:=20=ED=88=AC=ED=91=9C=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EB=B3=80=EA=B2=BD=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vote/repository/CustomVoteRepository.java | 2 ++ .../repository/CustomVoteRepositoryImpl.java | 26 +++++++++++++++++++ .../domain/vote/service/VoteService.java | 9 ++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java index 2e53479d..abc6870a 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java @@ -13,4 +13,6 @@ public interface CustomVoteRepository { List getVoteByStatusWithPage(VoteStatus status, Long voteTopicId, int size); List getVoteOptionByVoteId(Long voteId); + + List getVoteOptionByVoteIdOrderByCount(Long voteId); } diff --git a/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java index 003362c7..ed3147e9 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java @@ -100,6 +100,32 @@ public List getVoteOptionByVoteId(Long voteId) { .fetch(); } + @Override + public List getVoteOptionByVoteIdOrderByCount(Long voteId) { + return jpaQueryFactory + .select( + new QGetVoteOptionRes( + voteOption.id, + voteOption.optionText, + voteRecord.id.count().intValue() + ) + ) + .from(vote) + .join(voteOption) + .on(vote.voteTopicId.eq(voteOption.voteTopicId)) + .leftJoin(voteRecord) + .on(voteRecord.voteId.eq(vote.id) + .and(voteRecord.voteOptionId.eq(voteOption.id))) + .where( + vote.id.eq(voteId) + ) + .groupBy(voteOption.id, voteOption.optionText) + .orderBy( + voteRecord.id.count().intValue().asc() + ) + .fetch(); + } + private BooleanExpression gtVoteCursor(Long lastVoteId) { if (lastVoteId== null) return null; return vote.id.gt(lastVoteId); diff --git a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java index 8bdad85c..474af91f 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java @@ -20,6 +20,7 @@ import java.util.*; +import static devkor.com.teamcback.domain.vote.entity.VoteStatus.CLOSED; import static devkor.com.teamcback.global.response.ResultCode.*; @Service @@ -58,7 +59,7 @@ public GetVoteRes getVoteByPlace(Long voteTopicId, Long placeId) { /** * 투표 저장 */ - @UpdateScore(addScore = 2) + @UpdateScore(addScore = 3) @Transactional public SaveVoteRecordRes saveVoteRecord(Long userId, SaveVoteRecordReq req) { if(userId == null) throw new GlobalException(FORBIDDEN); @@ -87,6 +88,12 @@ else if(!Objects.equals(voteRecord.getVoteOptionId(), voteOption.getId())){ // voteRecord.setVoteOptionId(null); // 기존 투표 옵션을 null로 설정 (기록 남기기) } + // 투표 상태 변경 + List voteOptionList = voteOptionRepository.getVoteOptionByVoteIdOrderByCount(vote.getId()); // 투표 항목, 현황 + int min = voteOptionList.get(0).getVoteCount(); + int max = voteOptionList.get(voteOptionList.size() - 1).getVoteCount(); + if(max - min >= 30) vote.changeStatus(CLOSED); + return new SaveVoteRecordRes(); } From 7965d4c480326576c22c811780d5a1f4ff5fb145 Mon Sep 17 00:00:00 2001 From: yejin Date: Wed, 10 Sep 2025 21:22:16 +0900 Subject: [PATCH 17/17] =?UTF-8?q?Fix:=20=EB=AC=B4=ED=95=9C=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EC=9D=91=EB=8B=B5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vote/controller/AdminVoteController.java | 5 ++--- .../domain/vote/service/VoteService.java | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java b/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java index 090473ef..df0f19eb 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java @@ -5,6 +5,7 @@ import devkor.com.teamcback.domain.vote.entity.VoteStatus; import devkor.com.teamcback.domain.vote.service.VoteService; import devkor.com.teamcback.global.response.CommonResponse; +import devkor.com.teamcback.global.response.CursorPageRes; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; @@ -14,8 +15,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping("/api/admin/votes") @@ -50,7 +49,7 @@ public CommonResponse changeVoteStatus( content = @Content(schema = @Schema(implementation = CommonResponse.class))), }) @GetMapping() - public CommonResponse> getVoteList( + public CommonResponse> getVoteList( @Parameter(description = "투표 상태", example = "REFLECTED") @RequestParam(name = "status", required = false) VoteStatus status, @Parameter(description = "마지막 조회 투표 ID", example = "0") diff --git a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java index 474af91f..3608b0d3 100644 --- a/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java +++ b/src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java @@ -14,6 +14,7 @@ import devkor.com.teamcback.domain.vote.repository.VoteTopicRepository; import devkor.com.teamcback.global.annotation.UpdateScore; import devkor.com.teamcback.global.exception.exception.GlobalException; +import devkor.com.teamcback.global.response.CursorPageRes; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -101,10 +102,11 @@ else if(!Objects.equals(voteRecord.getVoteOptionId(), voteOption.getId())){ // * 투표 리스트 조회 */ @Transactional(readOnly = true) - public List getVoteList(VoteStatus status, Long lastVoteId, int size) { + public CursorPageRes getVoteList(VoteStatus status, Long lastVoteId, int size) { + List resList = new ArrayList<>(); - List voteList = voteOptionRepository.getVoteByStatusWithPage(status, lastVoteId, size); + List voteList = voteOptionRepository.getVoteByStatusWithPage(status, lastVoteId, size + 1); for(Vote vote : voteList) { // 투표 주제 @@ -119,7 +121,14 @@ public List getVoteList(VoteStatus status, Long lastVoteId, int size resList.add(new GetVoteRes(vote.getId(), vote.getStatus().toString(), voteTopic.getId(), voteTopic.getTopic(), place.getId(), place.getName(), voteOptionList)); } - return resList; + // CursorPageRes 응답 + boolean hasNext = voteList.size() > size; + if(hasNext) { + resList.remove(size); + } + Long lastCursorId = voteList.isEmpty() ? null : voteList.get(voteList.size() - 1).getId(); + + return new CursorPageRes<>(resList, hasNext, lastCursorId); } /**