diff --git a/src/main/java/com/studentvote/domain/auth/dto/response/CustomUserDetails.java b/src/main/java/com/studentvote/domain/auth/dto/response/CustomUserDetails.java index 102e245..1b10e90 100644 --- a/src/main/java/com/studentvote/domain/auth/dto/response/CustomUserDetails.java +++ b/src/main/java/com/studentvote/domain/auth/dto/response/CustomUserDetails.java @@ -3,7 +3,6 @@ import com.studentvote.domain.user.domain.User; import java.util.ArrayList; import java.util.Collection; -import java.util.List; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; diff --git a/src/main/java/com/studentvote/domain/candidateInfo/application/CandidateService.java b/src/main/java/com/studentvote/domain/candidateInfo/application/CandidateService.java index 48a428a..7ecacbc 100644 --- a/src/main/java/com/studentvote/domain/candidateInfo/application/CandidateService.java +++ b/src/main/java/com/studentvote/domain/candidateInfo/application/CandidateService.java @@ -4,6 +4,7 @@ import com.studentvote.domain.candidateInfo.domain.CandidateInfo; import com.studentvote.domain.candidateInfo.domain.CandidateInfoRepository; import com.studentvote.domain.candidateInfo.dto.request.RegisterCandidateInfoRequest; +import com.studentvote.domain.candidateInfo.dto.response.CandidateInfoListResponse; import com.studentvote.domain.candidateInfo.dto.response.RegisterCandidateInfoResponse; import com.studentvote.domain.user.domain.ApprovalStatus; import com.studentvote.domain.user.domain.User; @@ -14,6 +15,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.stream.Collectors; + @RequiredArgsConstructor @Service public class CandidateService { @@ -36,6 +40,26 @@ public RegisterCandidateInfoResponse registerCandidate(CustomUserDetails userDet return new RegisterCandidateInfoResponse(save.getId()); } + @Transactional(readOnly = true) + public CandidateInfoListResponse getCandidateInfo(CustomUserDetails userDetails,String governanceType) { + User user = userDetails.user(); + List candidateInfoList = candidateInfoRepository + .findAllCandidateByGovernanceType(user.getId(), governanceType); + + if (candidateInfoList.isEmpty()) throw new DefaultException(ErrorCode.CANDIDATE_INFO_NOT_FOUND); + + List candidateInfoResponseList = + candidateInfoList.stream() + .map(candidateInfo -> new CandidateInfoListResponse.CandidateInfoResponse( + candidateInfo.getCandidateName(), + candidateInfo.getCandidateContactAddress(), + candidateInfo.getCandidateInfoImage(), + candidateInfo.getLogoImage() + )) + .toList(); + return new CandidateInfoListResponse(candidateInfoResponseList); + } + private String makeFileName(User user, String option) { String fileName = "candidateInfo/" + user.getId() + "/"; if (option.equals("candidateInfoImage")) { @@ -57,4 +81,6 @@ private void validateUploadRequest(RegisterCandidateInfoRequest request) { throw new DefaultException(ErrorCode.INVALID_LOGO_IMAGE); } } + + } diff --git a/src/main/java/com/studentvote/domain/candidateInfo/domain/CandidateInfoRepository.java b/src/main/java/com/studentvote/domain/candidateInfo/domain/CandidateInfoRepository.java index 6834834..499e32a 100644 --- a/src/main/java/com/studentvote/domain/candidateInfo/domain/CandidateInfoRepository.java +++ b/src/main/java/com/studentvote/domain/candidateInfo/domain/CandidateInfoRepository.java @@ -1,6 +1,18 @@ package com.studentvote.domain.candidateInfo.domain; 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 CandidateInfoRepository extends JpaRepository { + + @Query("select c from CandidateInfo c " + + "join c.user u " + + "join u.governance g " + + "where u.id = :userId " + + "and g.governanceType = :governanceType") + List findAllCandidateByGovernanceType(@Param("userId") Long userId, @Param("governanceType") String governanceType); + } diff --git a/src/main/java/com/studentvote/domain/candidateInfo/dto/request/CandidateInfoListRequest.java b/src/main/java/com/studentvote/domain/candidateInfo/dto/request/CandidateInfoListRequest.java new file mode 100644 index 0000000..2c395ff --- /dev/null +++ b/src/main/java/com/studentvote/domain/candidateInfo/dto/request/CandidateInfoListRequest.java @@ -0,0 +1,4 @@ +package com.studentvote.domain.candidateInfo.dto.request; + +public record CandidateInfoListRequest() { +} diff --git a/src/main/java/com/studentvote/domain/candidateInfo/dto/request/RegisterCandidateInfoRequest.java b/src/main/java/com/studentvote/domain/candidateInfo/dto/request/RegisterCandidateInfoRequest.java index e8d363b..637ae25 100644 --- a/src/main/java/com/studentvote/domain/candidateInfo/dto/request/RegisterCandidateInfoRequest.java +++ b/src/main/java/com/studentvote/domain/candidateInfo/dto/request/RegisterCandidateInfoRequest.java @@ -4,7 +4,6 @@ import org.springframework.web.multipart.MultipartFile; public record RegisterCandidateInfoRequest( - ElectionType electionType, String candidateName, String candidateContactAddress, diff --git a/src/main/java/com/studentvote/domain/candidateInfo/dto/response/CandidateInfoListResponse.java b/src/main/java/com/studentvote/domain/candidateInfo/dto/response/CandidateInfoListResponse.java new file mode 100644 index 0000000..9f5da0d --- /dev/null +++ b/src/main/java/com/studentvote/domain/candidateInfo/dto/response/CandidateInfoListResponse.java @@ -0,0 +1,22 @@ +package com.studentvote.domain.candidateInfo.dto.response; + +import lombok.Builder; + +import java.util.List; + +public record CandidateInfoListResponse( + List candidateInfoList + + ) { + + @Builder + public record CandidateInfoResponse( + String candidateName, + String candidateContactAddress, + String candidateInfoImage, + String logoImage + + ) { + } + +} diff --git a/src/main/java/com/studentvote/domain/candidateInfo/presentation/CandidateController.java b/src/main/java/com/studentvote/domain/candidateInfo/presentation/CandidateController.java index 64f8578..e4d9c8a 100644 --- a/src/main/java/com/studentvote/domain/candidateInfo/presentation/CandidateController.java +++ b/src/main/java/com/studentvote/domain/candidateInfo/presentation/CandidateController.java @@ -2,15 +2,17 @@ import com.studentvote.domain.auth.dto.response.CustomUserDetails; import com.studentvote.domain.candidateInfo.application.CandidateService; +import com.studentvote.domain.candidateInfo.domain.CandidateInfo; import com.studentvote.domain.candidateInfo.dto.request.RegisterCandidateInfoRequest; +import com.studentvote.domain.candidateInfo.dto.response.CandidateInfoListResponse; import com.studentvote.domain.candidateInfo.dto.response.RegisterCandidateInfoResponse; import com.studentvote.global.payload.ResponseCustom; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RequiredArgsConstructor @@ -26,4 +28,12 @@ public ResponseCustom registerPoster(@Authenticat return ResponseCustom.OK(registerCandidateInfoResponse); } + @GetMapping("/candidateInfo/{governance}") + @Operation(summary = "단과대별 후보자 정보 조회", description = "사용자의 거버넌스 타입에 따른 후보자 정보를 조회합니다.") + public ResponseCustom getCandidateInfo(@AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable("governance") String governanceType) { + CandidateInfoListResponse candidateInfo = candidateService.getCandidateInfo(userDetails, governanceType); + return ResponseCustom.OK(candidateInfo); + } + + } diff --git a/src/main/java/com/studentvote/domain/common/Status.java b/src/main/java/com/studentvote/domain/common/Status.java index 457375a..149d744 100644 --- a/src/main/java/com/studentvote/domain/common/Status.java +++ b/src/main/java/com/studentvote/domain/common/Status.java @@ -1,5 +1,5 @@ package com.studentvote.domain.common; public enum Status { - ACTIVE, DELETE + ACTIVE, INACTIVE, DELETE } \ No newline at end of file diff --git a/src/main/java/com/studentvote/domain/poster/application/PosterService.java b/src/main/java/com/studentvote/domain/poster/application/PosterService.java index b2ef3fa..ed8acd9 100644 --- a/src/main/java/com/studentvote/domain/poster/application/PosterService.java +++ b/src/main/java/com/studentvote/domain/poster/application/PosterService.java @@ -1,7 +1,5 @@ package com.studentvote.domain.poster.application; - -import com.amazonaws.services.s3.AmazonS3; import com.studentvote.domain.auth.dto.response.CustomUserDetails; import com.studentvote.domain.poster.domain.Poster; import com.studentvote.domain.poster.domain.repository.PosterRepository; @@ -11,7 +9,6 @@ import com.studentvote.global.config.s3.S3Service; import com.studentvote.global.error.DefaultException; import com.studentvote.global.payload.ErrorCode; -import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -36,6 +33,18 @@ public Poster registerPoster(CustomUserDetails userDetails, RegisterPosterReques return poster; } + @Transactional + public Poster deletePoster(CustomUserDetails userDetails, Long posterId) { + User user = userDetails.user(); + Poster poster = posterRepository.findById(posterId).orElseThrow(() -> new DefaultException(ErrorCode.POSTER_NOT_FOUND)); + if (!poster.getUser().getId().equals(user.getId())) { + throw new DefaultException(ErrorCode.POSTER_ACCESS_DENIED); + } + s3Service.deleteImageFromS3(poster.getPosterImage()); + posterRepository.delete(poster); + return poster; + } + private String makeFileName(User user, MultipartFile image) { String originName = image.getOriginalFilename(); String ext = originName.substring(originName.lastIndexOf(".")); diff --git a/src/main/java/com/studentvote/domain/poster/domain/Poster.java b/src/main/java/com/studentvote/domain/poster/domain/Poster.java index 181b076..fd5a743 100644 --- a/src/main/java/com/studentvote/domain/poster/domain/Poster.java +++ b/src/main/java/com/studentvote/domain/poster/domain/Poster.java @@ -24,7 +24,7 @@ public class Poster { @Column(length = 1000) private String posterImage; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; diff --git a/src/main/java/com/studentvote/domain/poster/domain/repository/PosterRepository.java b/src/main/java/com/studentvote/domain/poster/domain/repository/PosterRepository.java index 80c45da..a8721a7 100644 --- a/src/main/java/com/studentvote/domain/poster/domain/repository/PosterRepository.java +++ b/src/main/java/com/studentvote/domain/poster/domain/repository/PosterRepository.java @@ -5,4 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface PosterRepository extends JpaRepository { + + + } diff --git a/src/main/java/com/studentvote/domain/poster/dto/request/RegisterPosterRequest.java b/src/main/java/com/studentvote/domain/poster/dto/request/RegisterPosterRequest.java index d0ecddf..a1dcbd8 100644 --- a/src/main/java/com/studentvote/domain/poster/dto/request/RegisterPosterRequest.java +++ b/src/main/java/com/studentvote/domain/poster/dto/request/RegisterPosterRequest.java @@ -4,6 +4,7 @@ public record RegisterPosterRequest( String posterName, + MultipartFile posterImage ) { diff --git a/src/main/java/com/studentvote/domain/poster/presentation/PosterController.java b/src/main/java/com/studentvote/domain/poster/presentation/PosterController.java index 65961e6..0e96f19 100644 --- a/src/main/java/com/studentvote/domain/poster/presentation/PosterController.java +++ b/src/main/java/com/studentvote/domain/poster/presentation/PosterController.java @@ -8,10 +8,7 @@ import com.studentvote.global.payload.ResponseCustom; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RequestMapping("api") @RequiredArgsConstructor @@ -25,5 +22,10 @@ public ResponseCustom registerPoster(@AuthenticationPrin Poster poster = posterService.registerPoster(userDetails, request); return ResponseCustom.OK(new RegisterPosterResponse(poster.getId(), poster.getPosterName(), poster.getPosterImage())); } - + + @DeleteMapping("poster/{posterId}") + public ResponseCustom deletePoster(@AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable Long posterId) { + Poster poster = posterService.deletePoster(userDetails, posterId); + return ResponseCustom.OK(poster.getPosterName()); + } } diff --git a/src/main/java/com/studentvote/domain/user/domain/Governance.java b/src/main/java/com/studentvote/domain/user/domain/Governance.java index 13dfe8b..a40327d 100644 --- a/src/main/java/com/studentvote/domain/user/domain/Governance.java +++ b/src/main/java/com/studentvote/domain/user/domain/Governance.java @@ -3,12 +3,9 @@ import com.studentvote.domain.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -26,7 +23,5 @@ public class Governance extends BaseEntity { private String governanceName; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id") - private User user; + private Long voteHeadquaterUserId; } diff --git a/src/main/java/com/studentvote/domain/user/domain/User.java b/src/main/java/com/studentvote/domain/user/domain/User.java index 2b59cc5..b84146a 100644 --- a/src/main/java/com/studentvote/domain/user/domain/User.java +++ b/src/main/java/com/studentvote/domain/user/domain/User.java @@ -5,9 +5,12 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -34,6 +37,10 @@ public class User extends BaseEntity { @Enumerated(EnumType.STRING) private ApprovalStatus approvalStatus; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "governance_id") + private Governance governance; + private User(String email, String password, String name) { this.email = email; diff --git a/src/main/java/com/studentvote/domain/user/domain/repository/GovernanceRepository.java b/src/main/java/com/studentvote/domain/user/domain/repository/GovernanceRepository.java new file mode 100644 index 0000000..e4394ba --- /dev/null +++ b/src/main/java/com/studentvote/domain/user/domain/repository/GovernanceRepository.java @@ -0,0 +1,7 @@ +package com.studentvote.domain.user.domain.repository; + +import com.studentvote.domain.user.domain.Governance; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GovernanceRepository extends JpaRepository { +} diff --git a/src/main/java/com/studentvote/domain/user/domain/repository/UserRepository.java b/src/main/java/com/studentvote/domain/user/domain/repository/UserRepository.java index 934e12d..2aa94c1 100644 --- a/src/main/java/com/studentvote/domain/user/domain/repository/UserRepository.java +++ b/src/main/java/com/studentvote/domain/user/domain/repository/UserRepository.java @@ -9,7 +9,7 @@ public interface UserRepository extends JpaRepository { Boolean existsByEmail(String email); - User findByUsername(String username); + Optional findByUsername(String username); Optional findByEmail(String email); diff --git a/src/main/java/com/studentvote/domain/vote/application/VoteService.java b/src/main/java/com/studentvote/domain/vote/application/VoteService.java new file mode 100644 index 0000000..599d701 --- /dev/null +++ b/src/main/java/com/studentvote/domain/vote/application/VoteService.java @@ -0,0 +1,124 @@ +package com.studentvote.domain.vote.application; + +import com.studentvote.domain.auth.dto.response.CustomUserDetails; +import com.studentvote.domain.common.Status; +import com.studentvote.domain.user.domain.Governance; +import com.studentvote.domain.user.domain.User; +import com.studentvote.domain.user.domain.repository.GovernanceRepository; +import com.studentvote.domain.user.domain.repository.UserRepository; +import com.studentvote.domain.vote.domain.Vote; +import com.studentvote.domain.vote.domain.VoteInformation; +import com.studentvote.domain.vote.domain.repository.VoteInformationRepository; +import com.studentvote.domain.vote.domain.repository.VoteRepository; +import com.studentvote.domain.vote.dto.request.CreateVoteRequest; +import com.studentvote.domain.vote.dto.request.RegisterVoteRateRequest; +import com.studentvote.domain.vote.dto.response.GetRateResponse; +import com.studentvote.domain.vote.exception.AlreadyExistVoteInformationException; +import com.studentvote.global.config.s3.S3Service; +import com.studentvote.global.payload.Message; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +@Transactional(readOnly = true) +public class VoteService { + + private static final String MASTER_USERNAME = "ADMIN"; + + private final S3Service s3Service; + + private final VoteInformationRepository voteInformationRepository; + private final VoteRepository voteRepository; + private final GovernanceRepository governanceRepository; + private final UserRepository userRepository; + + @Transactional + public Message createVote(CustomUserDetails userDetails, CreateVoteRequest createVoteRequest) { + + if (!userDetails.getUsername().equals(MASTER_USERNAME)) { + throw new IllegalArgumentException("접근 권한이 없습니다."); + } + + Boolean existsByStatus = voteInformationRepository.ExistsByStatus(Status.ACTIVE); + + if (existsByStatus) { + throw new AlreadyExistVoteInformationException(); + } + + String image = s3Service.uploadImageToS3(createVoteRequest.file()); + + VoteInformation voteInformation = VoteInformation.of(createVoteRequest.voteName(), + createVoteRequest.voteDateTime(), + createVoteRequest.voteDescription(), image); + + voteInformationRepository.save(voteInformation); + + return Message + .builder() + .message("선거 메타데이터 생성이 완료되었습니다.") + .build(); + } + + @Transactional + public Message reset(CustomUserDetails userDetails) { + + if (!userDetails.getUsername().equals(MASTER_USERNAME)) { + throw new IllegalArgumentException("접근 권한이 없습니다."); + } + + Optional byStatus = voteInformationRepository.findByStatus(Status.ACTIVE); + + if (byStatus.isPresent()) { + VoteInformation voteInformation = byStatus.get(); + voteInformation.updateStatus(Status.DELETE); + } + + /* + 여기에 삭제하고 싶은 모든 데이터 레포지토리 아래처럼 차례로 추가하면 됨. + */ + voteRepository.deleteAll(); + + return Message + .builder() + .message("선거 데이터를 초기화했습니다.") + .build(); + } + + @Transactional + public Message registerVoteRate(CustomUserDetails userDetails, Long departmentId, + RegisterVoteRateRequest registerVoteRateRequest) { + + System.out.println("userDetails.user().getEmail() = " + userDetails.user().getEmail()); + + User user = userRepository.findByEmail(userDetails.user().getEmail()) + .orElseThrow(() -> new IllegalArgumentException("등록되지 않은 유저입니다.")); + + Governance governance = governanceRepository.findById(departmentId) + .orElseThrow(() -> new NullPointerException("해당 학과/단과대를 찾을 수 없습니다.")); + + if (user.getId().equals(user.getGovernance().getVoteHeadquaterUserId()) && user.getGovernance().getId().equals(departmentId)) { + Vote vote = Vote.of(registerVoteRateRequest.voteCount(), registerVoteRateRequest.voteRate(), governance); + voteRepository.save(vote); + } + + return Message + .builder() + .message("저장이 완료되었습니다.") + .build(); + } + + public Page getRate(Long departmentId, Pageable pageable) { + + Governance governance = governanceRepository.findById(departmentId) + .orElseThrow(() -> new NullPointerException("해당 학과/단과대를 찾을 수 없습니다.")); + + Page getRateResponses = voteRepository.findAllByGovernance(governance, pageable); + + return getRateResponses; + } +} diff --git a/src/main/java/com/studentvote/domain/vote/domain/Vote.java b/src/main/java/com/studentvote/domain/vote/domain/Vote.java index 3a5c472..2c3391d 100644 --- a/src/main/java/com/studentvote/domain/vote/domain/Vote.java +++ b/src/main/java/com/studentvote/domain/vote/domain/Vote.java @@ -9,7 +9,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToOne; +import jakarta.persistence.ManyToOne; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -23,11 +23,21 @@ public class Vote extends BaseEntity { @Column(name = "id", updatable = false) private Long id; - private String description; + private Integer voteCount; private Double voteRate; - @OneToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "governance_id") private Governance governance; + + public Vote(Integer voteCount, Double voteRate, Governance governance) { + this.voteCount = voteCount; + this.voteRate = voteRate; + this.governance = governance; + } + + public static Vote of(Integer voteCount, Double voteRate, Governance governance) { + return new Vote(voteCount, voteRate, governance); + } } diff --git a/src/main/java/com/studentvote/domain/vote/domain/VoteInformation.java b/src/main/java/com/studentvote/domain/vote/domain/VoteInformation.java index 6cf8fc4..75d9da9 100644 --- a/src/main/java/com/studentvote/domain/vote/domain/VoteInformation.java +++ b/src/main/java/com/studentvote/domain/vote/domain/VoteInformation.java @@ -19,11 +19,23 @@ public class VoteInformation extends BaseEntity { @Column(name = "id", updatable = false) private Long id; - private String information; + private String name; private String dateTime; - private String location; + private String description; - private String prerequisite; + private String guideImageUrl; + + + public VoteInformation(String name, String dateTime, String description, String guideImageUrl) { + this.name = name; + this.dateTime = dateTime; + this.description = description; + this.guideImageUrl = guideImageUrl; + } + + public static VoteInformation of(String name, String dateTime, String description, String guideImageUrl) { + return new VoteInformation(name, dateTime, description, guideImageUrl); + } } diff --git a/src/main/java/com/studentvote/domain/vote/domain/repository/VoteInformationRepository.java b/src/main/java/com/studentvote/domain/vote/domain/repository/VoteInformationRepository.java new file mode 100644 index 0000000..73b84fd --- /dev/null +++ b/src/main/java/com/studentvote/domain/vote/domain/repository/VoteInformationRepository.java @@ -0,0 +1,14 @@ +package com.studentvote.domain.vote.domain.repository; + +import com.studentvote.domain.common.Status; +import com.studentvote.domain.vote.domain.VoteInformation; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface VoteInformationRepository extends JpaRepository { + @Query("SELECT CASE WHEN COUNT(v) > 0 THEN true ELSE false END FROM VoteInformation v WHERE v.status = :status") + Boolean ExistsByStatus(Status status); + + Optional findByStatus(Status status); +} diff --git a/src/main/java/com/studentvote/domain/vote/domain/repository/VoteQueryDslRepository.java b/src/main/java/com/studentvote/domain/vote/domain/repository/VoteQueryDslRepository.java new file mode 100644 index 0000000..c383671 --- /dev/null +++ b/src/main/java/com/studentvote/domain/vote/domain/repository/VoteQueryDslRepository.java @@ -0,0 +1,10 @@ +package com.studentvote.domain.vote.domain.repository; + +import com.studentvote.domain.user.domain.Governance; +import com.studentvote.domain.vote.dto.response.GetRateResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface VoteQueryDslRepository { + Page findAllByGovernance(Governance governance, Pageable pageable); +} diff --git a/src/main/java/com/studentvote/domain/vote/domain/repository/VoteQueryDslRepositoryImpl.java b/src/main/java/com/studentvote/domain/vote/domain/repository/VoteQueryDslRepositoryImpl.java new file mode 100644 index 0000000..05e8d8e --- /dev/null +++ b/src/main/java/com/studentvote/domain/vote/domain/repository/VoteQueryDslRepositoryImpl.java @@ -0,0 +1,46 @@ +package com.studentvote.domain.vote.domain.repository; + +import static com.studentvote.domain.vote.domain.QVote.vote; + +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.studentvote.domain.user.domain.Governance; +import com.studentvote.domain.vote.dto.response.GetRateResponse; +import com.studentvote.domain.vote.dto.response.QGetRateResponse; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class VoteQueryDslRepositoryImpl implements VoteQueryDslRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public Page findAllByGovernance(Governance governance, Pageable pageable) { + + List results = queryFactory + .select(new QGetRateResponse( + vote.createdAt, + vote.voteCount, + vote.voteRate + )) + .from(vote) + .where(vote.governance.eq(governance)) + .orderBy(vote.createdAt.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery countQuery = queryFactory + .select(vote.count()) + .from(vote) + .where(vote.governance.eq(governance)); + + return PageableExecutionUtils.getPage(results, pageable, () -> countQuery.fetch().size()); + } +} diff --git a/src/main/java/com/studentvote/domain/vote/domain/repository/VoteRepository.java b/src/main/java/com/studentvote/domain/vote/domain/repository/VoteRepository.java new file mode 100644 index 0000000..74ddabc --- /dev/null +++ b/src/main/java/com/studentvote/domain/vote/domain/repository/VoteRepository.java @@ -0,0 +1,10 @@ +package com.studentvote.domain.vote.domain.repository; + +import com.studentvote.domain.user.domain.Governance; +import com.studentvote.domain.vote.domain.Vote; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VoteRepository extends JpaRepository, VoteQueryDslRepository { + Optional findByGovernance(Governance governance); +} diff --git a/src/main/java/com/studentvote/domain/vote/dto/request/CreateVoteRequest.java b/src/main/java/com/studentvote/domain/vote/dto/request/CreateVoteRequest.java new file mode 100644 index 0000000..1beb71f --- /dev/null +++ b/src/main/java/com/studentvote/domain/vote/dto/request/CreateVoteRequest.java @@ -0,0 +1,11 @@ +package com.studentvote.domain.vote.dto.request; + +import org.springframework.web.multipart.MultipartFile; + +public record CreateVoteRequest( + String voteName, + String voteDateTime, + String voteDescription, + MultipartFile file +) { +} diff --git a/src/main/java/com/studentvote/domain/vote/dto/request/RegisterVoteRateRequest.java b/src/main/java/com/studentvote/domain/vote/dto/request/RegisterVoteRateRequest.java new file mode 100644 index 0000000..b7bf767 --- /dev/null +++ b/src/main/java/com/studentvote/domain/vote/dto/request/RegisterVoteRateRequest.java @@ -0,0 +1,7 @@ +package com.studentvote.domain.vote.dto.request; + +public record RegisterVoteRateRequest( + int voteCount, + double voteRate +) { +} diff --git a/src/main/java/com/studentvote/domain/vote/dto/response/GetRateResponse.java b/src/main/java/com/studentvote/domain/vote/dto/response/GetRateResponse.java new file mode 100644 index 0000000..1b6daf6 --- /dev/null +++ b/src/main/java/com/studentvote/domain/vote/dto/response/GetRateResponse.java @@ -0,0 +1,18 @@ +package com.studentvote.domain.vote.dto.response; + +import com.querydsl.core.annotations.QueryProjection; +import java.time.LocalDateTime; + +public record GetRateResponse( + LocalDateTime localDateTime, + int voteCount, + double voteRate +) { + + @QueryProjection + public GetRateResponse(LocalDateTime localDateTime, int voteCount, double voteRate) { + this.localDateTime = localDateTime; + this.voteCount = voteCount; + this.voteRate = voteRate; + } +} diff --git a/src/main/java/com/studentvote/domain/vote/exception/AlreadyExistVoteInformationException.java b/src/main/java/com/studentvote/domain/vote/exception/AlreadyExistVoteInformationException.java new file mode 100644 index 0000000..7397465 --- /dev/null +++ b/src/main/java/com/studentvote/domain/vote/exception/AlreadyExistVoteInformationException.java @@ -0,0 +1,7 @@ +package com.studentvote.domain.vote.exception; + +public class AlreadyExistVoteInformationException extends RuntimeException { + public AlreadyExistVoteInformationException() { + super("이미 선거 정보가 등록되어 있습니다."); + } +} diff --git a/src/main/java/com/studentvote/domain/vote/presentation/VoteController.java b/src/main/java/com/studentvote/domain/vote/presentation/VoteController.java new file mode 100644 index 0000000..bcafdd3 --- /dev/null +++ b/src/main/java/com/studentvote/domain/vote/presentation/VoteController.java @@ -0,0 +1,66 @@ +package com.studentvote.domain.vote.presentation; + +import com.studentvote.domain.auth.dto.response.CustomUserDetails; +import com.studentvote.domain.vote.application.VoteService; +import com.studentvote.domain.vote.dto.request.CreateVoteRequest; +import com.studentvote.domain.vote.dto.request.RegisterVoteRateRequest; +import com.studentvote.domain.vote.dto.response.GetRateResponse; +import com.studentvote.global.payload.Message; +import com.studentvote.global.payload.ResponseCustom; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "Vote", description = "Vote API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/vote") +public class VoteController { + + private final VoteService voteService; + + @Operation(summary = "선거 메타데이터 등록", description = "선거 관련 정보를 등록합니다.") + @PostMapping + public ResponseCustom createVote( + @AuthenticationPrincipal CustomUserDetails userDetails, + @ModelAttribute CreateVoteRequest createVoteRequest + ) { + return ResponseCustom.OK(voteService.createVote(userDetails, createVoteRequest)); + } + + @Operation(summary = "선거 초기화", description = "선거 관련 데이터를 초기화힙니다.") + @PostMapping("/reset") + public ResponseCustom reset(@AuthenticationPrincipal CustomUserDetails userDetails) { + return ResponseCustom.OK(voteService.reset(userDetails)); + } + + @Operation(summary = "투표율 현황 등록", description = "투표 단과대 또는 학과 별 투표 현황을 등록합니다.") + @PostMapping("/rate/{departmentId}") + public ResponseCustom registerVoteRate( + @AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable Long departmentId, + @RequestBody RegisterVoteRateRequest registerVoteRateRequest + ) { + return ResponseCustom.OK(voteService.registerVoteRate(userDetails, departmentId, registerVoteRateRequest)); + } + + @Operation(summary = "투표율 조회", description = "선거(단과대/학과) 별 투표율을 조회합니다.") + @GetMapping("/rate/{departmentId}") + public ResponseCustom> getRate( + @PathVariable Long departmentId, + @PageableDefault(size = 10, page = 0) Pageable pageable + ) { + return ResponseCustom.OK(voteService.getRate(departmentId, pageable)); + } +} diff --git a/src/main/java/com/studentvote/global/config/s3/S3Service.java b/src/main/java/com/studentvote/global/config/s3/S3Service.java index b179270..9bef169 100644 --- a/src/main/java/com/studentvote/global/config/s3/S3Service.java +++ b/src/main/java/com/studentvote/global/config/s3/S3Service.java @@ -61,4 +61,13 @@ public String uploadImageToS3(MultipartFile image, String fileName) { } } + public void deleteImageFromS3(String imageUrl) { + try { + String fileName = imageUrl.substring(imageUrl.lastIndexOf("/") + 1); + amazonS3.deleteObject(bucket, fileName); + } catch (Exception e) { + throw new RuntimeException("Image delete failed", e); + } + } + } diff --git a/src/main/java/com/studentvote/global/payload/ErrorCode.java b/src/main/java/com/studentvote/global/payload/ErrorCode.java index c2237f3..c96f520 100644 --- a/src/main/java/com/studentvote/global/payload/ErrorCode.java +++ b/src/main/java/com/studentvote/global/payload/ErrorCode.java @@ -17,7 +17,10 @@ public enum ErrorCode { INVALID_ELECTIONREGULATION(400,null,"선거규정이 누락되었습니다."), INVALID_CANDIDATE_INFO_IMAGE(400,null,"입후보자 공고 이미지가 누락되었습니다."), INVALID_LOGO_IMAGE(400,null,"로고 이미지가 누락 되었습니다"), - STATUS_NOT_APPROVED(400,null,"후보자로 승인되지 않은 계정입니다"); + STATUS_NOT_APPROVED(400,null,"후보자로 승인되지 않은 계정입니다"), + CANDIDATE_INFO_NOT_FOUND(400,null,"단과대학에 소속하는 후보자가 없습니다"), + POSTER_NOT_FOUND(400,null,"해당하는 게시물이 없습니다."), + POSTER_ACCESS_DENIED(400,null,"해당 포스터를 삭제할 권한이 없습니다"); private final String code; private final String message;