diff --git a/src/main/java/com/umc/naoman/domain/agenda/controller/AgendaController.java b/src/main/java/com/umc/naoman/domain/agenda/controller/AgendaController.java index c4f0131c..7e9fc459 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/controller/AgendaController.java +++ b/src/main/java/com/umc/naoman/domain/agenda/controller/AgendaController.java @@ -3,6 +3,8 @@ import com.umc.naoman.domain.agenda.converter.AgendaConverter; import com.umc.naoman.domain.agenda.dto.AgendaRequest; import com.umc.naoman.domain.agenda.dto.AgendaResponse; +import com.umc.naoman.domain.agenda.dto.AgendaResponse.AgendaDetailInfo; +import com.umc.naoman.domain.agenda.dto.AgendaResponse.PagedAgendaDetailInfo; import com.umc.naoman.domain.agenda.entity.Agenda; import com.umc.naoman.domain.agenda.service.AgendaService; import com.umc.naoman.domain.member.entity.Member; @@ -11,17 +13,34 @@ import com.umc.naoman.global.result.code.AgendaResultCode; import com.umc.naoman.global.security.annotation.LoginMember; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + +import static com.umc.naoman.global.result.code.AgendaResultCode.GET_AGENDA_LIST; + @RestController +@RequestMapping("/agendas") +@Tag(name = "04. 안건 관련 API", description = "안건 관련 API입니다.") @RequiredArgsConstructor @Slf4j public class AgendaController { @@ -29,7 +48,7 @@ public class AgendaController { private final AgendaService agendaService; private final AgendaConverter agendaConverter; - @PostMapping("/agendas") + @PostMapping @Operation(summary = "안건 생성 API", description = "[request]\n shareGroupId, title, 안건에 올릴 PhotoId 리스트" + "\n[response]\n 생성된 안건의 agendaId, 생성시간 createdAt") @ApiResponses({ @@ -46,5 +65,46 @@ public ResultResponse createAgenda(@RequestBody @Vali Agenda agenda = agendaService.createAgenda(member,request); return ResultResponse.of(AgendaResultCode.CREATE_AGENDA, agendaConverter.toAgendaInfo(agenda)); } + + @GetMapping() + @Operation(summary = "특정 공유 그룹의 안건 목록 조회 API", description = "특정 공유 그룹의 안건 목록을 조회하는 API입니다.") + @Parameters(value = { + @Parameter(name = "shareGroupId", description = "특정 안건 id를 입력해 주세요."), + @Parameter(name = "page", description = "조회할 페이지 번호를 입력해주세요.(0번부터)"), + @Parameter(name = "size", description = "한 페이지에 나타낼 안건 개수를 입력해 주세요.") + }) + public ResultResponse getAgendaList(@RequestParam("shareGroupId") Long shareGroupId, + @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) + @Parameter(hidden = true) Pageable pageable, + @LoginMember Member member) { + PagedAgendaDetailInfo pagedAgendaDetailInfo = agendaService.getAgendaList(shareGroupId, member, pageable); + return ResultResponse.of(GET_AGENDA_LIST, pagedAgendaDetailInfo); + } + + + @GetMapping("/{agendaId}") + @Operation(summary = "안건 상세 조회 API", description = "agendaId로 안건 상세를 조회하는 API입니다.") + @Parameters(value = { + @Parameter(name = "agendaId", description = "특정 안건 id를 입력해 주세요.") + }) + public ResultResponse getAgendaDetail(@PathVariable(name = "agendaId") Long agendaId, + @LoginMember Member member) { + Agenda agenda = agendaService.getAgendaDetailInfo(agendaId, member); + + return ResultResponse.of(AgendaResultCode.AGENDA_DETAIL, + agendaConverter.toAgendaDetailInfo(agenda)); + } + + @DeleteMapping("/{agendaId}") + @Operation(summary = "안건 삭제 API", description = "agendaId로 안건 삭제하는 API입니다.") + @Parameters(value = { + @Parameter(name = "agendaId", description = "특정 안건 id를 입력해 주세요.") + }) + public ResultResponse deleteAgenda(@PathVariable(name = "agendaId") Long agendaId, + @LoginMember Member member) { + Agenda agenda = agendaService.deleteAgenda(agendaId); + + return ResultResponse.of(AgendaResultCode.AGENDA_DETAIL, agendaConverter.toAgendaInfo(agenda)); + } } diff --git a/src/main/java/com/umc/naoman/domain/agenda/converter/AgendaConverter.java b/src/main/java/com/umc/naoman/domain/agenda/converter/AgendaConverter.java index 45f0cf7e..831bd92e 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/converter/AgendaConverter.java +++ b/src/main/java/com/umc/naoman/domain/agenda/converter/AgendaConverter.java @@ -1,14 +1,24 @@ package com.umc.naoman.domain.agenda.converter; +import com.umc.naoman.domain.agenda.dto.AgendaPhotoResponse; import com.umc.naoman.domain.agenda.dto.AgendaResponse; +import com.umc.naoman.domain.agenda.dto.AgendaResponse.AgendaDetailInfo; import com.umc.naoman.domain.agenda.entity.Agenda; import com.umc.naoman.domain.shareGroup.entity.Profile; import com.umc.naoman.domain.shareGroup.entity.ShareGroup; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.stereotype.Component; +import java.util.List; +import java.util.stream.Collectors; + @Component +@RequiredArgsConstructor public class AgendaConverter { + private final AgendaPhotoConverter agendaPhotoConverter; + public AgendaResponse.AgendaInfo toAgendaInfo(Agenda agenda){ return AgendaResponse.AgendaInfo.builder() .agendaId(agenda.getId()) @@ -23,4 +33,31 @@ public Agenda toEntity(Profile profile, String title, ShareGroup shareGroup){ .shareGroup(shareGroup) .build(); } + + public AgendaDetailInfo toAgendaDetailInfo(Agenda agenda) { + List agendaPhotoInfoList = agenda.getAgendaPhotoList() + .stream() + .map(agendaPhotoConverter::toAgendaPhotoInfo) + .collect(Collectors.toList()); + + return AgendaDetailInfo.builder() + .agendaId(agenda.getId()) + .title(agenda.getTitle()) + .agendaPhotoInfoList(agendaPhotoInfoList) + .build(); + } + + public AgendaResponse.PagedAgendaDetailInfo toPageAgendaDetailInfo(Page agendaList) { + List agendaDetailInfoList = agendaList.stream() + .map(agenda -> toAgendaDetailInfo(agenda)) + .toList(); + + return AgendaResponse.PagedAgendaDetailInfo.builder() + .agendaDetailInfoList(agendaDetailInfoList) + .totalPages(agendaList.getTotalPages()) + .totalElements(agendaList.getTotalElements()) + .isFirst(agendaList.isFirst()) + .isLast(agendaList.isLast()) + .build(); + } } diff --git a/src/main/java/com/umc/naoman/domain/agenda/converter/AgendaPhotoConverter.java b/src/main/java/com/umc/naoman/domain/agenda/converter/AgendaPhotoConverter.java index 1e3faf55..b4eb00d0 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/converter/AgendaPhotoConverter.java +++ b/src/main/java/com/umc/naoman/domain/agenda/converter/AgendaPhotoConverter.java @@ -1,9 +1,12 @@ package com.umc.naoman.domain.agenda.converter; +import com.umc.naoman.domain.agenda.dto.AgendaPhotoResponse; import com.umc.naoman.domain.agenda.entity.Agenda; import com.umc.naoman.domain.agenda.entity.AgendaPhoto; import com.umc.naoman.domain.photo.entity.Photo; +import org.springframework.stereotype.Component; +@Component public class AgendaPhotoConverter { public static AgendaPhoto toEntity(Agenda agenda, Photo photo) { @@ -12,4 +15,12 @@ public static AgendaPhoto toEntity(Agenda agenda, Photo photo) { .photo(photo) .build(); } + + public AgendaPhotoResponse.AgendaPhotoInfo toAgendaPhotoInfo(AgendaPhoto agendaPhoto) { + return AgendaPhotoResponse.AgendaPhotoInfo + .builder() + .agendaPhotoId(agendaPhoto.getId()) + .url(agendaPhoto.getPhoto().getUrl()) + .build(); + } } diff --git a/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaPhotoResponse.java b/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaPhotoResponse.java new file mode 100644 index 00000000..25ece9df --- /dev/null +++ b/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaPhotoResponse.java @@ -0,0 +1,18 @@ +package com.umc.naoman.domain.agenda.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +public abstract class AgendaPhotoResponse { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class AgendaPhotoInfo { + private Long agendaPhotoId; + private String url; + } +} diff --git a/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaRequest.java b/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaRequest.java index 78520c13..4117c9e1 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaRequest.java +++ b/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaRequest.java @@ -21,6 +21,6 @@ public static class CreateAgendaRequest { @NotNull private String title; @Size(min = 2, max = 6, message = "안건에 등록하는 사진은 최소 2개 최대 6개로 한정 시켜 주세요.") - private List agendasPhotoList; + private List photoIdList; } } diff --git a/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaResponse.java b/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaResponse.java index 64482756..85bce4c2 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaResponse.java +++ b/src/main/java/com/umc/naoman/domain/agenda/dto/AgendaResponse.java @@ -1,11 +1,14 @@ package com.umc.naoman.domain.agenda.dto; import com.fasterxml.jackson.annotation.JsonFormat; +import com.umc.naoman.domain.agenda.dto.AgendaPhotoResponse.AgendaPhotoInfo; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; import java.time.LocalDateTime; +import java.util.List; public abstract class AgendaResponse { @@ -17,4 +20,26 @@ public static class AgendaInfo { @JsonFormat(pattern = "yyyy-MM-dd") private LocalDateTime createdAt; } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class AgendaDetailInfo { + private Long agendaId; + private String title; + private List agendaPhotoInfoList; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class PagedAgendaDetailInfo { + private List agendaDetailInfoList; + private int totalPages; + private long totalElements; // 해당 조건에 부합하는 요소의 총 개수 + private boolean isFirst; // 첫 페이지 여부 + private boolean isLast; // 마지막 페이지 여부 + } } diff --git a/src/main/java/com/umc/naoman/domain/agenda/entity/Agenda.java b/src/main/java/com/umc/naoman/domain/agenda/entity/Agenda.java index db0ad566..feb08eb8 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/entity/Agenda.java +++ b/src/main/java/com/umc/naoman/domain/agenda/entity/Agenda.java @@ -2,11 +2,13 @@ import com.umc.naoman.domain.shareGroup.entity.Profile; import com.umc.naoman.domain.shareGroup.entity.ShareGroup; +import com.umc.naoman.domain.vote.entity.Vote; import com.umc.naoman.global.entity.BaseTimeEntity; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.persistence.Column; import jakarta.persistence.ManyToOne; @@ -20,6 +22,9 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.SQLRestriction; +import java.util.ArrayList; +import java.util.List; + @Entity @Table(name = "agendas") @SQLRestriction("deleted_at is NULL") @@ -40,4 +45,16 @@ public class Agenda extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "profile_id") private Profile profile; + + @OneToMany(mappedBy = "agenda") + @Builder.Default + private List agendaPhotoList = new ArrayList<>(); + + public void delete() { + //agendaPhoto 삭제 + for (AgendaPhoto agendaPhoto : agendaPhotoList) { + agendaPhoto.delete(); + } + super.delete(); + } } diff --git a/src/main/java/com/umc/naoman/domain/agenda/entity/AgendaPhoto.java b/src/main/java/com/umc/naoman/domain/agenda/entity/AgendaPhoto.java index 026298ae..e09f69b1 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/entity/AgendaPhoto.java +++ b/src/main/java/com/umc/naoman/domain/agenda/entity/AgendaPhoto.java @@ -1,11 +1,15 @@ package com.umc.naoman.domain.agenda.entity; import com.umc.naoman.domain.photo.entity.Photo; +import com.umc.naoman.domain.vote.entity.Vote; import com.umc.naoman.global.entity.BaseTimeEntity; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.SQLRestriction; +import java.util.ArrayList; +import java.util.List; + @Entity @Table(name = "agendas_photos") @SQLRestriction("deleted_at is NULL") @@ -24,4 +28,16 @@ public class AgendaPhoto extends BaseTimeEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "photo_id") private Photo photo; -} + + @OneToMany(mappedBy = "agendaPhoto") + @Builder.Default + private List voteList = new ArrayList<>(); + + public void delete() { + //agendaPhoto 삭제 + for (Vote vote : voteList) { + vote.delete(); + } + super.delete(); + } +} \ No newline at end of file diff --git a/src/main/java/com/umc/naoman/domain/agenda/repository/AgendaPhotoRepository.java b/src/main/java/com/umc/naoman/domain/agenda/repository/AgendaPhotoRepository.java index 8c295210..0c1ac74d 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/repository/AgendaPhotoRepository.java +++ b/src/main/java/com/umc/naoman/domain/agenda/repository/AgendaPhotoRepository.java @@ -4,6 +4,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface AgendaPhotoRepository extends JpaRepository { + List findByAgendaId(Long agendaId); } diff --git a/src/main/java/com/umc/naoman/domain/agenda/repository/AgendaRepository.java b/src/main/java/com/umc/naoman/domain/agenda/repository/AgendaRepository.java index 6e4875ff..55279737 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/repository/AgendaRepository.java +++ b/src/main/java/com/umc/naoman/domain/agenda/repository/AgendaRepository.java @@ -1,9 +1,13 @@ package com.umc.naoman.domain.agenda.repository; import com.umc.naoman.domain.agenda.entity.Agenda; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface AgendaRepository extends JpaRepository { + Page findByShareGroupId(Long shareGroupId, Pageable pageable); + } diff --git a/src/main/java/com/umc/naoman/domain/agenda/service/AgendaPhotoService.java b/src/main/java/com/umc/naoman/domain/agenda/service/AgendaPhotoService.java index a41722ed..219caa7c 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/service/AgendaPhotoService.java +++ b/src/main/java/com/umc/naoman/domain/agenda/service/AgendaPhotoService.java @@ -1,9 +1,13 @@ package com.umc.naoman.domain.agenda.service; import com.umc.naoman.domain.agenda.entity.Agenda; +import com.umc.naoman.domain.agenda.entity.AgendaPhoto; import java.util.List; public interface AgendaPhotoService { void saveAgendaPhotoList(Agenda agenda, List photos); + + AgendaPhoto findAgendaPhoto(Long agendaPhotoId); + List findAgendaPhotoList(Long agendaId); } diff --git a/src/main/java/com/umc/naoman/domain/agenda/service/AgendaPhotoServiceImpl.java b/src/main/java/com/umc/naoman/domain/agenda/service/AgendaPhotoServiceImpl.java index 48324b32..4f28b6c5 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/service/AgendaPhotoServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/agenda/service/AgendaPhotoServiceImpl.java @@ -2,21 +2,25 @@ import com.umc.naoman.domain.agenda.converter.AgendaPhotoConverter; import com.umc.naoman.domain.agenda.entity.Agenda; +import com.umc.naoman.domain.agenda.entity.AgendaPhoto; import com.umc.naoman.domain.agenda.repository.AgendaPhotoRepository; import com.umc.naoman.domain.photo.entity.Photo; import com.umc.naoman.domain.photo.service.PhotoService; +import com.umc.naoman.global.error.BusinessException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import static com.umc.naoman.global.error.code.AgendaErrorCode.AGENDA_PHOTO_NOT_FOUND; + @Service +@Transactional(readOnly = true) @RequiredArgsConstructor public class AgendaPhotoServiceImpl implements AgendaPhotoService { - - private final AgendaPhotoRepository agendaPhotoRepository; private final PhotoService photoService; + private final AgendaPhotoRepository agendaPhotoRepository; @Override @Transactional @@ -26,4 +30,15 @@ public void saveAgendaPhotoList(Agenda agenda, List photos) { agendaPhotoRepository.save(AgendaPhotoConverter.toEntity(agenda, photo)); } } + + @Override + public AgendaPhoto findAgendaPhoto(Long agendaPhotoId) { + return agendaPhotoRepository.findById(agendaPhotoId) + .orElseThrow(() -> new BusinessException(AGENDA_PHOTO_NOT_FOUND)); + } + + @Override + public List findAgendaPhotoList(Long agendaId) { + return agendaPhotoRepository.findByAgendaId(agendaId); + } } diff --git a/src/main/java/com/umc/naoman/domain/agenda/service/AgendaService.java b/src/main/java/com/umc/naoman/domain/agenda/service/AgendaService.java index 8d6a1c47..fa345cb9 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/service/AgendaService.java +++ b/src/main/java/com/umc/naoman/domain/agenda/service/AgendaService.java @@ -1,11 +1,16 @@ package com.umc.naoman.domain.agenda.service; import com.umc.naoman.domain.agenda.dto.AgendaRequest; +import com.umc.naoman.domain.agenda.dto.AgendaResponse.PagedAgendaDetailInfo; import com.umc.naoman.domain.agenda.entity.Agenda; import com.umc.naoman.domain.member.entity.Member; +import org.springframework.data.domain.Pageable; public interface AgendaService { + Agenda createAgenda(Member member, AgendaRequest.CreateAgendaRequest request); + Agenda getAgendaDetailInfo(Long agendaId, Member member); + PagedAgendaDetailInfo getAgendaList(Long shareGroupId, Member member, Pageable pageable); + Agenda deleteAgenda(Long agendaId); Agenda findAgenda(Long agendaId); - Agenda createAgenda(Member member, AgendaRequest.CreateAgendaRequest request); } diff --git a/src/main/java/com/umc/naoman/domain/agenda/service/AgendaServiceImpl.java b/src/main/java/com/umc/naoman/domain/agenda/service/AgendaServiceImpl.java index 17d90912..4019aee0 100644 --- a/src/main/java/com/umc/naoman/domain/agenda/service/AgendaServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/agenda/service/AgendaServiceImpl.java @@ -2,7 +2,12 @@ import com.umc.naoman.domain.agenda.converter.AgendaConverter; import com.umc.naoman.domain.agenda.dto.AgendaRequest; +import com.umc.naoman.domain.agenda.dto.AgendaRequest.CreateAgendaRequest; +import com.umc.naoman.domain.agenda.dto.AgendaResponse; +import com.umc.naoman.domain.agenda.dto.AgendaResponse.AgendaDetailInfo; +import com.umc.naoman.domain.agenda.dto.AgendaResponse.PagedAgendaDetailInfo; import com.umc.naoman.domain.agenda.entity.Agenda; +import com.umc.naoman.domain.agenda.entity.AgendaPhoto; import com.umc.naoman.domain.agenda.repository.AgendaRepository; import com.umc.naoman.domain.member.entity.Member; import com.umc.naoman.domain.shareGroup.entity.Profile; @@ -11,20 +16,23 @@ import com.umc.naoman.global.error.BusinessException; import com.umc.naoman.global.error.code.AgendaErrorCode; 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; +import java.util.List; + @Service +@Transactional(readOnly = true) @RequiredArgsConstructor public class AgendaServiceImpl implements AgendaService { - private final AgendaRepository agendaRepository; private final ShareGroupService shareGroupService; private final AgendaPhotoService agendaPhotoService; private final AgendaConverter agendaConverter; @Override - @Transactional(readOnly = true) public Agenda findAgenda(Long agendaId) { return agendaRepository.findById(agendaId) .orElseThrow(() -> new BusinessException(AgendaErrorCode.AGENDA_NOT_FOUND_BY_AGENDA_ID)); @@ -32,13 +40,40 @@ public Agenda findAgenda(Long agendaId) { @Override @Transactional - public Agenda createAgenda(Member member, AgendaRequest.CreateAgendaRequest request) { + public Agenda createAgenda(Member member, CreateAgendaRequest request) { Long shareGroupId = request.getShareGroupId(); ShareGroup shareGroup = shareGroupService.findShareGroup(shareGroupId); Profile profile = shareGroupService.findProfile(shareGroupId, member.getId()); - Agenda newAgenda = agendaConverter.toEntity(profile,request.getTitle(),shareGroup); - agendaPhotoService.saveAgendaPhotoList(newAgenda,request.getAgendasPhotoList()); + Agenda newAgenda = agendaConverter.toEntity(profile, request.getTitle(),shareGroup); + agendaPhotoService.saveAgendaPhotoList(newAgenda, request.getPhotoIdList()); return agendaRepository.save(newAgenda); } -} + + @Override + public PagedAgendaDetailInfo getAgendaList(Long shareGroupId, Member member, Pageable pageable) { + ShareGroup shareGroup = shareGroupService.findShareGroup(shareGroupId); + + Page agendaList = agendaRepository.findByShareGroupId(shareGroupId, pageable); + return agendaConverter.toPageAgendaDetailInfo(agendaList); + } + + @Override + public Agenda getAgendaDetailInfo(Long agendaId, Member member) { + Agenda agenda = findAgenda(agendaId); + + shareGroupService.findProfile(agenda.getShareGroup().getId(), + member.getId()); + + return agenda; + } + + @Override + @Transactional + public Agenda deleteAgenda(Long agendaId) { + Agenda agenda = findAgenda(agendaId); + + agenda.delete(); + return agenda; + } +} \ No newline at end of file diff --git a/src/main/java/com/umc/naoman/domain/member/controller/AuthController.java b/src/main/java/com/umc/naoman/domain/member/controller/AuthController.java index 9a60aa71..c8866559 100644 --- a/src/main/java/com/umc/naoman/domain/member/controller/AuthController.java +++ b/src/main/java/com/umc/naoman/domain/member/controller/AuthController.java @@ -5,6 +5,7 @@ import com.umc.naoman.domain.member.dto.MemberRequest.SignupRequest; import com.umc.naoman.domain.member.dto.MemberResponse.CheckMemberRegistration; import com.umc.naoman.domain.member.dto.MemberResponse.LoginInfo; +import com.umc.naoman.domain.member.entity.SocialType; import com.umc.naoman.domain.member.service.MemberService; import com.umc.naoman.global.error.BusinessException; import com.umc.naoman.global.result.ResultResponse; @@ -30,7 +31,7 @@ @RestController @RequestMapping("/auth") -@Tag(name = "인증,인가 관련 API", description = "회원의 회원가입 및 로그인 등을 처리하는 API입니다.") +@Tag(name = "00. 인증,인가 관련 API", description = "회원의 회원가입 및 로그인 등을 처리하는 API입니다.") @RequiredArgsConstructor public class AuthController { private final MemberService memberService; @@ -62,7 +63,7 @@ public ResultResponse login(@Valid @RequestBody LoginRequest request) return ResultResponse.of(LOGIN, memberService.login(request)); } - @GetMapping("/check-registration") + @PostMapping("/check-registration") @Operation(summary = "회원가입 여부 조회 API", description = "authId와 플랫폼명을 통해, 해당 정보와 일치하는 회원의 가입 여부를 조회하는 API입니다.") public ResultResponse checkSignup(@Valid @RequestBody LoginRequest request) { return ResultResponse.of(CHECK_MEMBER_REGISTRATION, memberService.checkRegistration(request)); diff --git a/src/main/java/com/umc/naoman/domain/member/controller/MemberController.java b/src/main/java/com/umc/naoman/domain/member/controller/MemberController.java index d619895e..e3090902 100644 --- a/src/main/java/com/umc/naoman/domain/member/controller/MemberController.java +++ b/src/main/java/com/umc/naoman/domain/member/controller/MemberController.java @@ -24,7 +24,7 @@ @RestController @RequestMapping("/members") -@Tag(name = "회원 API", description = "회원 도메인의 API입니다.") +@Tag(name = "01. 회원 API", description = "회원 도메인의 API입니다.") @RequiredArgsConstructor public class MemberController { private final MemberService memberService; diff --git a/src/main/java/com/umc/naoman/domain/notification/controller/NotificationController.java b/src/main/java/com/umc/naoman/domain/notification/controller/NotificationController.java index 186b024c..bc7f94c7 100644 --- a/src/main/java/com/umc/naoman/domain/notification/controller/NotificationController.java +++ b/src/main/java/com/umc/naoman/domain/notification/controller/NotificationController.java @@ -5,12 +5,17 @@ import com.umc.naoman.domain.notification.dto.NotificationRequest; import com.umc.naoman.domain.notification.dto.NotificationResponse; import com.umc.naoman.domain.notification.entity.Notification; +import com.umc.naoman.domain.notification.service.FcmService; import com.umc.naoman.domain.notification.service.NotificationService; import com.umc.naoman.global.result.ResultResponse; import com.umc.naoman.global.result.code.NotificationResultCode; import com.umc.naoman.global.security.annotation.LoginMember; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -22,15 +27,27 @@ @RestController -@RequiredArgsConstructor @RequestMapping("/notifications") +@Tag(name = "06. 알림 관련 API", description = "나ㅇ만 서비스의 알림 관련 API입니다.") +@RequiredArgsConstructor public class NotificationController { private final NotificationService notificationService; + private final FcmService fcmService; + private final NotificationConverter notificationConverter; @PostMapping("/token") + @Operation( + summary = "fcm 토큰 업로드", + description = "안드로이드가 fcm 토큰을 서버에 업로드하는 API입니다.", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + content = @Content( + schema = @Schema(implementation = NotificationRequest.FcmToken.class) + ) + ) + ) public ResultResponse registerFcmToken(@RequestBody NotificationRequest.FcmToken fcmToken, @LoginMember Member member){ - + fcmService.saveFcmToken(member, fcmToken.getToken()); return ResultResponse.of(NotificationResultCode.REGISTER_FCM_TOKEN,null); } @@ -39,39 +56,54 @@ public ResultResponse registerFcmToken(@RequestBody NotificationRequest.Fc @Parameter(name = "page", description = "조회할 페이지를 입력해 주세요.(0번부터 시작)"), @Parameter(name = "size", description = "한 페이지에 나타낼 알림 개수를 입력해주세요.") }) - public ResultResponse getNotifications(@LoginMember Member member, + @Operation(summary = "자신의 모든 알림 조회", description = "자신의 모든 알림을 조회하는 API입니다.") + public ResultResponse getNotificationList(@LoginMember Member member, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) @Parameter(hidden = true) Pageable pageable){ Page notificationPage = notificationService.getNotificationList(member, pageable); return ResultResponse.of(NotificationResultCode.GET_MY_NOTIFICATION, - NotificationConverter.toNotificationInfo(notificationPage)); + notificationConverter.toPagedNotificationInfo(notificationPage)); } @GetMapping("/unread") - public ResultResponse getIsUnread(@LoginMember Member member){ + @Operation(summary = "읽지 않은 알림 유무 조회", + description = "읽지 않은 알림 유무 조회하는 API입니다.", + parameters = {}) + public ResultResponse getIsUnread(@LoginMember Member member){ List notificationList = notificationService.isUnreadNotification(member); return ResultResponse.of(NotificationResultCode.CHECK_MY_UNREAD_NOTIFICATION, - NotificationConverter.toUnreadNotification(notificationList)); + notificationConverter.toIsUnread(notificationList)); } @PostMapping("/acknowledgements") + @Operation(summary = "모든 알림 읽음 처리", + description = "자신의 모든 알림을 읽음 처리하는 API입니다.", + parameters = {}) public ResultResponse setMyNotificationRead(@LoginMember Member member){ List notificationList = notificationService.setMyNotificationRead(member); return ResultResponse.of(NotificationResultCode.READ_ALL_MY_NOTIFICATION, - NotificationConverter.toNotificationAcknowledgedCount(notificationList)); + notificationConverter.toNotificationAcknowledgedCount(notificationList)); } @DeleteMapping("/{notificationId}") + @Operation(summary = "특정 알림 삭제", + description = "특정 알림을 삭제하는 API입니다.", + parameters = { + @Parameter(name = "notificationId", description = "삭제할 알림의 Id", required = true, schema = @Schema(type = "long")) + }) public ResultResponse deleteNotification(@PathVariable Long notificationId, @LoginMember Member member){ long deletedCount = notificationService.deleteNotification(member,notificationId); return ResultResponse.of(NotificationResultCode.DELETE_MY_NOTIFICATION, - NotificationConverter.toNotificationAcknowledgedCount(deletedCount)); + notificationConverter.toNotificationAcknowledgedCount(deletedCount)); } @DeleteMapping + @Operation(summary = "모든 알림 삭제", + description = "자신의 모든 알림을 삭제하는 API입니다.", + parameters = {}) public ResultResponse deleteAllNotification(@LoginMember Member member){ long deletedCount = notificationService.deleteNotificationAll(member); return ResultResponse.of(NotificationResultCode.DELETE_MY_NOTIFICATION, - NotificationConverter.toNotificationDeletedCount(deletedCount)); + notificationConverter.toNotificationDeletedCount(deletedCount)); } } diff --git a/src/main/java/com/umc/naoman/domain/notification/converter/NotificationConverter.java b/src/main/java/com/umc/naoman/domain/notification/converter/NotificationConverter.java index 146c5e9d..50812760 100644 --- a/src/main/java/com/umc/naoman/domain/notification/converter/NotificationConverter.java +++ b/src/main/java/com/umc/naoman/domain/notification/converter/NotificationConverter.java @@ -6,13 +6,15 @@ import com.umc.naoman.domain.notification.entity.DeviceToken; import com.umc.naoman.domain.notification.entity.Notification; import org.springframework.data.domain.Page; +import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; +@Component public class NotificationConverter { - public static NotificationResponse.NotificationInfo notificationInfo(Notification notification){ + public NotificationResponse.NotificationInfo toNotificationInfo(Notification notification){ return NotificationResponse.NotificationInfo.builder() .body(notification.getMessage()) .createdAt(notification.getCreatedAt()) @@ -20,10 +22,10 @@ public static NotificationResponse.NotificationInfo notificationInfo(Notificatio .url(notification.makeNotificationInfoURL()) //다형성으로 각기 다른 알림이 적절한 URL 만들도록 오버라이딩. .build(); } - public static NotificationResponse.PagedNotificationInfo toNotificationInfo( + public NotificationResponse.PagedNotificationInfo toPagedNotificationInfo( Page notificationList){ List notificationInfoList - = notificationList.stream().map(NotificationConverter::notificationInfo).collect(Collectors.toList()); + = notificationList.stream().map(this::toNotificationInfo).collect(Collectors.toList()); return NotificationResponse.PagedNotificationInfo.builder() .isLast(notificationList.isLast()) @@ -34,30 +36,30 @@ public static NotificationResponse.PagedNotificationInfo toNotificationInfo( .build(); } - public static NotificationResponse.UnreadNotification toUnreadNotification(List notificationList){ - return NotificationResponse.UnreadNotification.builder() + public NotificationResponse.IsUnread toIsUnread(List notificationList){ + return NotificationResponse.IsUnread.builder() .isUnread(!notificationList.isEmpty()) .build(); } - public static NotificationResponse.NotificationAcknowledgeCount toNotificationAcknowledgedCount(List notificationList){ + public NotificationResponse.NotificationAcknowledgeCount toNotificationAcknowledgedCount(List notificationList){ return NotificationResponse.NotificationAcknowledgeCount.builder() .acknowledgedCount((long)notificationList.size()) .build(); } - public static NotificationResponse.NotificationAcknowledgeCount toNotificationAcknowledgedCount(Long updateCount){ + public NotificationResponse.NotificationAcknowledgeCount toNotificationAcknowledgedCount(Long updateCount){ return NotificationResponse.NotificationAcknowledgeCount.builder() .acknowledgedCount(updateCount) .build(); } - public static NotificationResponse.NotificationDeletedCount toNotificationDeletedCount(Long updateCount){ + public NotificationResponse.NotificationDeletedCount toNotificationDeletedCount(Long updateCount){ return NotificationResponse.NotificationDeletedCount.builder() .deletedCount(updateCount) .build(); } - public static DeviceToken toDeviceToken(Member member, NotificationRequest.FcmToken fcmToken){ + public DeviceToken toDeviceToken(Member member, NotificationRequest.FcmToken fcmToken){ return DeviceToken.builder() .member(member) .fcmToken(fcmToken.getToken()) diff --git a/src/main/java/com/umc/naoman/domain/notification/dto/NotificationResponse.java b/src/main/java/com/umc/naoman/domain/notification/dto/NotificationResponse.java index 6e1dccbc..54c6fc35 100644 --- a/src/main/java/com/umc/naoman/domain/notification/dto/NotificationResponse.java +++ b/src/main/java/com/umc/naoman/domain/notification/dto/NotificationResponse.java @@ -38,7 +38,7 @@ public static class NotificationInfo { @Getter @NoArgsConstructor @AllArgsConstructor - public static class UnreadNotification { + public static class IsUnread { Boolean isUnread; } diff --git a/src/main/java/com/umc/naoman/domain/notification/service/FcmService.java b/src/main/java/com/umc/naoman/domain/notification/service/FcmService.java index 37ee41e7..3d1cf515 100644 --- a/src/main/java/com/umc/naoman/domain/notification/service/FcmService.java +++ b/src/main/java/com/umc/naoman/domain/notification/service/FcmService.java @@ -1,5 +1,7 @@ package com.umc.naoman.domain.notification.service; +import com.umc.naoman.domain.member.entity.Member; + public interface FcmService { - void saveFcmToken(Long memberId, String fcmToken); + void saveFcmToken(Member member, String fcmToken); } diff --git a/src/main/java/com/umc/naoman/domain/notification/service/FcmServiceImpl.java b/src/main/java/com/umc/naoman/domain/notification/service/FcmServiceImpl.java index de8e0e75..2fe7a04b 100644 --- a/src/main/java/com/umc/naoman/domain/notification/service/FcmServiceImpl.java +++ b/src/main/java/com/umc/naoman/domain/notification/service/FcmServiceImpl.java @@ -1,5 +1,6 @@ package com.umc.naoman.domain.notification.service; +import com.umc.naoman.domain.member.entity.Member; import com.umc.naoman.domain.notification.entity.DeviceToken; import com.umc.naoman.domain.notification.repository.FcmTokenRepository; import lombok.RequiredArgsConstructor; @@ -13,10 +14,9 @@ public class FcmServiceImpl implements FcmService{ private final FcmTokenRepository fcmTokenRepository; @Override - public void saveFcmToken(Long memberId, String fcmToken) { - //멤버 리파지토리 개발 후 수정해야됨 + public void saveFcmToken(Member member, String fcmToken) { DeviceToken deviceToken = DeviceToken.builder() - .member(null) + .member(member) .fcmToken(fcmToken) .build(); fcmTokenRepository.save(deviceToken); diff --git a/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java b/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java index 7fb48fd7..41ef017c 100644 --- a/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java +++ b/src/main/java/com/umc/naoman/domain/photo/controller/PhotoController.java @@ -9,21 +9,17 @@ import com.umc.naoman.global.result.ResultResponse; import com.umc.naoman.global.security.annotation.LoginMember; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.core.io.ByteArrayResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; -import org.springframework.http.ContentDisposition; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -37,7 +33,7 @@ @RestController @RequestMapping("/photos") @RequiredArgsConstructor -@Tag(name = "사진 관련 API", description = "사진 업로드, 조회, 삭제, 다운로드를 처리하는 API입니다.") +@Tag(name = "03. 사진 관련 API", description = "사진 업로드, 조회, 삭제, 다운로드를 처리하는 API입니다.") public class PhotoController { private final PhotoService photoService; @@ -61,7 +57,13 @@ public ResultResponse uploadPhotoList(@Valid @Req @GetMapping("/all") @Operation(summary = "특정 공유그룹의 전체 사진 조회 API", description = "특정 공유 그룹의 전체 사진을 조회하는 API입니다.") - public ResultResponse getAllPhotoListByShareGroup(@RequestParam Long shareGroupId, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable, + @Parameters(value = { + @Parameter(name = "page", description = "조회할 페이지를 입력해 주세요.(0번부터 시작)"), + @Parameter(name = "size", description = "한 페이지에 나타낼 사진 개수를 입력해주세요.") + }) + public ResultResponse getAllPhotoListByShareGroup(@RequestParam Long shareGroupId, + @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) + @Parameter(hidden = true) Pageable pageable, @LoginMember Member member) { Page allPhotoListByShareGroup = photoService.getAllPhotoList(shareGroupId, member, pageable); return ResultResponse.of(RETRIEVE_PHOTO, photoConverter.toPhotoListInfo(allPhotoListByShareGroup)); diff --git a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java index 78f7411d..2fbe0dc0 100644 --- a/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java +++ b/src/main/java/com/umc/naoman/domain/photo/converter/PhotoConverter.java @@ -62,6 +62,7 @@ public PhotoResponse.PhotoInfo toPhotoInfo(Photo photo) { String rawUrl = photo.getUrl(); return PhotoResponse.PhotoInfo.builder() + .photoId(photo.getId()) .rawPhotoUrl(rawUrl) .w200PhotoUrl(createResizedPhotoUrl(rawUrl, W200_PATH_PREFIX)) .w400PhotoUrl(createResizedPhotoUrl(rawUrl, W400_PATH_PREFIX)) diff --git a/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java b/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java index 35dad94e..cd5a5538 100644 --- a/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java +++ b/src/main/java/com/umc/naoman/domain/photo/dto/PhotoResponse.java @@ -56,6 +56,7 @@ public static class PagedPhotoInfo { @AllArgsConstructor @NoArgsConstructor public static class PhotoInfo { + private Long photoId; private String rawPhotoUrl; private String w200PhotoUrl; private String w400PhotoUrl; diff --git a/src/main/java/com/umc/naoman/domain/shareGroup/controller/ShareGroupController.java b/src/main/java/com/umc/naoman/domain/shareGroup/controller/ShareGroupController.java index d38379f8..7dd8be6e 100644 --- a/src/main/java/com/umc/naoman/domain/shareGroup/controller/ShareGroupController.java +++ b/src/main/java/com/umc/naoman/domain/shareGroup/controller/ShareGroupController.java @@ -35,7 +35,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("/shareGroups") -@Tag(name = "공유그룹 관련 API", description = "공유그룹 생성, 참여, 조회, 삭제 등을 처리하는 API입니다.") +@Tag(name = "02. 공유그룹 관련 API", description = "공유그룹 생성, 참여, 조회, 삭제 등을 처리하는 API입니다.") public class ShareGroupController { private final ShareGroupService shareGroupService; private final ShareGroupConverter shareGroupConverter; diff --git a/src/main/java/com/umc/naoman/domain/shareGroup/entity/Profile.java b/src/main/java/com/umc/naoman/domain/shareGroup/entity/Profile.java index 9053b458..54007531 100644 --- a/src/main/java/com/umc/naoman/domain/shareGroup/entity/Profile.java +++ b/src/main/java/com/umc/naoman/domain/shareGroup/entity/Profile.java @@ -1,6 +1,8 @@ package com.umc.naoman.domain.shareGroup.entity; +import com.umc.naoman.domain.agenda.entity.Agenda; import com.umc.naoman.domain.member.entity.Member; +import com.umc.naoman.domain.vote.entity.Vote; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -11,6 +13,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -20,6 +23,8 @@ import org.hibernate.annotations.SQLRestriction; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Entity @Table(name = "profiles") @@ -50,7 +55,14 @@ public class Profile { @Column(name = "deleted_at") private LocalDateTime deletedAt; + @OneToMany(mappedBy = "profile") + @Builder.Default + private List voteList = new ArrayList<>(); + public void delete() { + for (Vote vote: voteList) { + vote.delete(); + } this.deletedAt = LocalDateTime.now(); } diff --git a/src/main/java/com/umc/naoman/domain/shareGroup/entity/ShareGroup.java b/src/main/java/com/umc/naoman/domain/shareGroup/entity/ShareGroup.java index 6070729d..e8339662 100644 --- a/src/main/java/com/umc/naoman/domain/shareGroup/entity/ShareGroup.java +++ b/src/main/java/com/umc/naoman/domain/shareGroup/entity/ShareGroup.java @@ -1,5 +1,6 @@ package com.umc.naoman.domain.shareGroup.entity; +import com.umc.naoman.domain.agenda.entity.Agenda; import com.umc.naoman.global.entity.BaseTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -38,12 +39,18 @@ public class ShareGroup extends BaseTimeEntity { private String inviteCode; @OneToMany(mappedBy = "shareGroup") @Builder.Default - private List profiles = new ArrayList<>(); + private List profileList = new ArrayList<>(); + @OneToMany(mappedBy = "shareGroup") + @Builder.Default + private List agendaList = new ArrayList<>(); public void delete() { - super.delete(); - for (Profile profile : profiles) { + for (Profile profile : profileList) { profile.delete(); } + for (Agenda agenda : agendaList) { + agenda.delete(); + } + super.delete(); } } diff --git a/src/main/java/com/umc/naoman/domain/shareGroup/service/ShareGroupService.java b/src/main/java/com/umc/naoman/domain/shareGroup/service/ShareGroupService.java index 6c968beb..03938aa6 100644 --- a/src/main/java/com/umc/naoman/domain/shareGroup/service/ShareGroupService.java +++ b/src/main/java/com/umc/naoman/domain/shareGroup/service/ShareGroupService.java @@ -20,6 +20,6 @@ public interface ShareGroupService { List findProfileListByShareGroupId(Long shareGroupId); List findProfileListByMemberId(Long memberId); Profile findProfile(Long profileId); - Profile findProfile(Long shareGroupId, Long memberID); + Profile findProfile(Long shareGroupId, Long memberId); boolean doesProfileExist(Long shareGroupId, Long memberId); } diff --git a/src/main/java/com/umc/naoman/domain/vote/controller/VoteController.java b/src/main/java/com/umc/naoman/domain/vote/controller/VoteController.java new file mode 100644 index 00000000..89b0c74f --- /dev/null +++ b/src/main/java/com/umc/naoman/domain/vote/controller/VoteController.java @@ -0,0 +1,53 @@ +package com.umc.naoman.domain.vote.controller; + +import com.umc.naoman.domain.member.entity.Member; +import com.umc.naoman.domain.vote.dto.VoteRequest.GenerateVoteListRequest; +import com.umc.naoman.domain.vote.dto.VoteResponse.AgendaPhotoVoteDetails; +import com.umc.naoman.domain.vote.dto.VoteResponse.VoteIdList; +import com.umc.naoman.domain.vote.service.VoteService; +import com.umc.naoman.global.result.ResultResponse; +import com.umc.naoman.global.security.annotation.LoginMember; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static com.umc.naoman.global.result.code.VoteResultCode.GENERATE_VOTE; + +@RestController +@Tag(name = "05. 투표 API", description = "안건에 대한 투표 행위를 관리하는 API입니다.") +@RequiredArgsConstructor +public class VoteController { + private final VoteService voteService; + + @PostMapping("/agendas/{agendaId}/vote") + @Operation(summary = "안건에 투표하기 API", description = "특정 안건에 대하여 투표하는 API입니다. 한 번에 여러 사진에 투표가 가능합니다.") + @Parameters(value = { + @Parameter(name = "agendaId", description = "투표를 진행할 안건의 agendaId를 입력해 주세요.") + }) + public ResultResponse generateVoteList(@PathVariable("agendaId") Long agendaId, + @Valid @RequestBody GenerateVoteListRequest request, + @LoginMember Member member) { + return ResultResponse.of(GENERATE_VOTE, voteService.generateVoteList(agendaId, request, member)); + } + + @GetMapping("/agendas/{agendaId}/vote") + @Operation(summary = "특정 안건의 투표 현황 조회 API", description = "특정 안건에 대한 투표 현항을 조회하는 API입니다.") + @Parameters(value = { + @Parameter(name = "agendaId", description = "투표 현황을 조회할 안건의 agendaId를 입력해 주세요.") + }) + public ResultResponse> getVoteList(@PathVariable("agendaId") Long agendaId, + @LoginMember Member member) { + return ResultResponse.of(GENERATE_VOTE, voteService.getVoteList(agendaId, member)); + } + +} diff --git a/src/main/java/com/umc/naoman/domain/vote/converter/VoteConverter.java b/src/main/java/com/umc/naoman/domain/vote/converter/VoteConverter.java new file mode 100644 index 00000000..d9d05564 --- /dev/null +++ b/src/main/java/com/umc/naoman/domain/vote/converter/VoteConverter.java @@ -0,0 +1,64 @@ +package com.umc.naoman.domain.vote.converter; + +import com.umc.naoman.domain.agenda.entity.AgendaPhoto; +import com.umc.naoman.domain.shareGroup.converter.ShareGroupConverter; +import com.umc.naoman.domain.shareGroup.entity.Profile; +import com.umc.naoman.domain.vote.dto.VoteResponse.AgendaPhotoVoteDetails; +import com.umc.naoman.domain.vote.dto.VoteResponse.VoteId; +import com.umc.naoman.domain.vote.dto.VoteResponse.VoteIdList; +import com.umc.naoman.domain.vote.dto.VoteResponse.VoteInfo; +import com.umc.naoman.domain.vote.entity.Vote; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class VoteConverter { + private final ShareGroupConverter shareGroupConverter; + + public Vote toEntity(String comment, Profile profile, AgendaPhoto agendaPhoto) { + return Vote.builder() + .comment(comment) + .profile(profile) + .agendaPhoto(agendaPhoto) + .build(); + } + + public VoteId toVoteId(Long voteId) { + return new VoteId(voteId); + } + + public VoteIdList toVoteIdList(List voteList) { + List voteIdList = voteList.stream() + .map(vote -> vote.getId()) + .collect(Collectors.toList()); + + return new VoteIdList(voteIdList); + } + + public VoteInfo toVoteInfo(Vote vote, Profile viewerProfile) { + Long voterProfileId = vote.getProfile().getId(); + Long viewerProfileId = viewerProfile.getId(); + boolean isMine = voterProfileId.equals(viewerProfileId); + + return VoteInfo.builder() + .voteId(vote.getId()) + .comment(vote.getComment()) + .profileInfo(shareGroupConverter.toProfileInfo(vote.getProfile())) + .agendaPhotoId(vote.getAgendaPhoto().getId()) + .isMine(isMine) + .votedAt(vote.getVotedAt()) + .build(); + } + + public AgendaPhotoVoteDetails toAgendaPhotoVoteDetails(Long agendaPhotoId, List voteInfoList) { + return AgendaPhotoVoteDetails.builder() + .agendaPhotoId(agendaPhotoId) + .voteInfoList(voteInfoList) + .voteCount(voteInfoList.size()) + .build(); + } +} diff --git a/src/main/java/com/umc/naoman/domain/vote/dto/VoteRequest.java b/src/main/java/com/umc/naoman/domain/vote/dto/VoteRequest.java new file mode 100644 index 00000000..2965c84a --- /dev/null +++ b/src/main/java/com/umc/naoman/domain/vote/dto/VoteRequest.java @@ -0,0 +1,33 @@ +package com.umc.naoman.domain.vote.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +public abstract class VoteRequest { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class GenerateVoteListRequest { + @NotEmpty(message = "최소 1개의 투표를 한 후 요청해야 합니다.") + private List voteInfoList; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class GenerateVoteRequest { + @Size(max = 15, message = "투표 코멘트는 최대 15자까지 작성할 수 있습니다.") + private String comment; + @NotNull(message = "투표하려는 안건의 사진에 대한 agendaPhotoId 값을 필수로 입력해 주세요.") + private Long agendaPhotoId; + } +} diff --git a/src/main/java/com/umc/naoman/domain/vote/dto/VoteResponse.java b/src/main/java/com/umc/naoman/domain/vote/dto/VoteResponse.java new file mode 100644 index 00000000..ceabfe30 --- /dev/null +++ b/src/main/java/com/umc/naoman/domain/vote/dto/VoteResponse.java @@ -0,0 +1,55 @@ +package com.umc.naoman.domain.vote.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.umc.naoman.domain.shareGroup.dto.ShareGroupResponse.ProfileInfo; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +public abstract class VoteResponse { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class VoteIdList { + @Builder.Default + private List voteIdList = new ArrayList<>(); + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class VoteId { + private Long voteId; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class VoteInfo { + private Long voteId; + private String comment; + private ProfileInfo profileInfo; + private Long agendaPhotoId; + private Boolean isMine; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime votedAt; + } + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class AgendaPhotoVoteDetails { + private Long agendaPhotoId; + @Builder.Default + private List voteInfoList = new ArrayList<>(); + private int voteCount; + } +} diff --git a/src/main/java/com/umc/naoman/domain/vote/entity/Vote.java b/src/main/java/com/umc/naoman/domain/vote/entity/Vote.java index d3914de3..0ab3d29d 100644 --- a/src/main/java/com/umc/naoman/domain/vote/entity/Vote.java +++ b/src/main/java/com/umc/naoman/domain/vote/entity/Vote.java @@ -1,7 +1,7 @@ package com.umc.naoman.domain.vote.entity; import com.umc.naoman.domain.agenda.entity.AgendaPhoto; -import com.umc.naoman.domain.member.entity.Member; +import com.umc.naoman.domain.shareGroup.entity.Profile; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; @@ -26,7 +26,7 @@ @Entity @EntityListeners(AuditingEntityListener.class) @Table(name = "votes") -@SQLRestriction("deleted_at is NULL") +@SQLRestriction("canceled_at is NULL") @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -38,8 +38,8 @@ public class Vote { private Long id; private String comment; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "member_id") - private Member member; + @JoinColumn(name = "profile_id") + private Profile profile; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "agendas_photos_id") private AgendaPhoto agendaPhoto; diff --git a/src/main/java/com/umc/naoman/domain/vote/repository/VoteRepository.java b/src/main/java/com/umc/naoman/domain/vote/repository/VoteRepository.java new file mode 100644 index 00000000..2457d7c9 --- /dev/null +++ b/src/main/java/com/umc/naoman/domain/vote/repository/VoteRepository.java @@ -0,0 +1,16 @@ +package com.umc.naoman.domain.vote.repository; + +import com.umc.naoman.domain.vote.entity.Vote; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface VoteRepository extends JpaRepository { + Boolean existsByProfileIdAndAgendaPhotoId(Long profileId, Long agendaPhotoId); + List findByAgendaPhotoId(Long agendaPhotoId); + List findByAgendaPhotoIdIn(List agendaPhotoIdList); +} diff --git a/src/main/java/com/umc/naoman/domain/vote/service/VoteService.java b/src/main/java/com/umc/naoman/domain/vote/service/VoteService.java new file mode 100644 index 00000000..f7d0e94f --- /dev/null +++ b/src/main/java/com/umc/naoman/domain/vote/service/VoteService.java @@ -0,0 +1,13 @@ +package com.umc.naoman.domain.vote.service; + +import com.umc.naoman.domain.member.entity.Member; +import com.umc.naoman.domain.vote.dto.VoteRequest.GenerateVoteListRequest; +import com.umc.naoman.domain.vote.dto.VoteResponse.AgendaPhotoVoteDetails; +import com.umc.naoman.domain.vote.dto.VoteResponse.VoteIdList; + +import java.util.List; + +public interface VoteService { + VoteIdList generateVoteList(Long agendaId, GenerateVoteListRequest request, Member member); + List getVoteList(Long agendaId, Member member); +} diff --git a/src/main/java/com/umc/naoman/domain/vote/service/VoteServiceImpl.java b/src/main/java/com/umc/naoman/domain/vote/service/VoteServiceImpl.java new file mode 100644 index 00000000..2e170174 --- /dev/null +++ b/src/main/java/com/umc/naoman/domain/vote/service/VoteServiceImpl.java @@ -0,0 +1,90 @@ +package com.umc.naoman.domain.vote.service; + +import com.umc.naoman.domain.agenda.entity.Agenda; +import com.umc.naoman.domain.agenda.entity.AgendaPhoto; +import com.umc.naoman.domain.agenda.service.AgendaPhotoService; +import com.umc.naoman.domain.agenda.service.AgendaService; +import com.umc.naoman.domain.member.entity.Member; +import com.umc.naoman.domain.shareGroup.entity.Profile; +import com.umc.naoman.domain.shareGroup.entity.Role; +import com.umc.naoman.domain.shareGroup.service.ShareGroupService; +import com.umc.naoman.domain.vote.converter.VoteConverter; +import com.umc.naoman.domain.vote.dto.VoteRequest.GenerateVoteListRequest; +import com.umc.naoman.domain.vote.dto.VoteRequest.GenerateVoteRequest; +import com.umc.naoman.domain.vote.dto.VoteResponse.AgendaPhotoVoteDetails; +import com.umc.naoman.domain.vote.dto.VoteResponse.VoteIdList; +import com.umc.naoman.domain.vote.dto.VoteResponse.VoteInfo; +import com.umc.naoman.domain.vote.entity.Vote; +import com.umc.naoman.domain.vote.repository.VoteRepository; +import com.umc.naoman.global.error.BusinessException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +import static com.umc.naoman.global.error.code.VoteErrorCode.DUPLICATE_VOTE; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class VoteServiceImpl implements VoteService { + private final ShareGroupService shareGroupService; + private final AgendaService agendaService; + private final AgendaPhotoService agendaPhotoService; + private final VoteRepository voteRepository; + private final VoteConverter voteConverter; + + @Override + @Transactional + public VoteIdList generateVoteList(Long agendaId, GenerateVoteListRequest request, Member member) { + Agenda agenda = agendaService.findAgenda(agendaId); // 안건의 존재 여부 확인 + Profile profile = shareGroupService.findProfile(agenda.getShareGroup().getId(), member.getId()); + + List voteList = request.getVoteInfoList().stream() + .map(voteInfo -> generateVote(voteInfo, profile)) + .collect(Collectors.toList()); + + voteRepository.saveAll(voteList); + return voteConverter.toVoteIdList(voteList); + } + + private Vote generateVote(GenerateVoteRequest request, Profile profile) { + AgendaPhoto agendaPhoto = agendaPhotoService.findAgendaPhoto(request.getAgendaPhotoId()); + checkDuplicateVote(profile, agendaPhoto); + + return voteConverter.toEntity(request.getComment(), profile, agendaPhoto); + } + + // 해당 사진에 대한 투표를 이미 했는지 확인한다 + private void checkDuplicateVote(Profile profile, AgendaPhoto agendaPhoto) { + if (voteRepository.existsByProfileIdAndAgendaPhotoId(profile.getId(), agendaPhoto.getId())) { + throw new BusinessException(DUPLICATE_VOTE); + } + } + + @Override + public List getVoteList(Long agendaId, Member member) { + Agenda agenda = agendaService.findAgenda(agendaId); // 안건 존재 여부 확인 + // 안건과 연관관계에 있는 공유그룹을 이용해 요청한 회원의 프로필을 가져온다 + // 해당 회원이 해당 공유 그룹의 그룹원인지도 확인 + Profile viewerProfile = shareGroupService.findProfile(agenda.getShareGroup().getId(), member.getId()); + + List agendaPhotoList = agendaPhotoService.findAgendaPhotoList(agendaId); + return agendaPhotoList.stream() + .map(agendaPhoto -> getVoteList(agendaPhoto.getId(), viewerProfile)) + .toList(); + } + + // 특정 안건 중 특정 사진에 대한 투표 목록 조회 + private AgendaPhotoVoteDetails getVoteList(Long agendaPhotoId, Profile viewerProfile) { + List voteList = voteRepository.findByAgendaPhotoId(agendaPhotoId); + + List voteInfoList = voteList.stream() + .map(vote -> voteConverter.toVoteInfo(vote, viewerProfile)) + .collect(Collectors.toList()); + + return voteConverter.toAgendaPhotoVoteDetails(agendaPhotoId, voteInfoList); + } +} diff --git a/src/main/java/com/umc/naoman/global/error/code/AgendaErrorCode.java b/src/main/java/com/umc/naoman/global/error/code/AgendaErrorCode.java index c14042ed..fbd5601f 100644 --- a/src/main/java/com/umc/naoman/global/error/code/AgendaErrorCode.java +++ b/src/main/java/com/umc/naoman/global/error/code/AgendaErrorCode.java @@ -8,6 +8,8 @@ @RequiredArgsConstructor public enum AgendaErrorCode implements ErrorCode { AGENDA_NOT_FOUND_BY_AGENDA_ID(404, "EA001", "해당 agendaId를 가진 안건이 존재하지 않습니다."), + AGENDA_PHOTO_NOT_FOUND(404, "EA002", "해당 agendaPhotoId를 가진 안건 후보로 올라간 사진이 존재하지 않습니다."), + ; private final int status; diff --git a/src/main/java/com/umc/naoman/global/error/code/VoteErrorCode.java b/src/main/java/com/umc/naoman/global/error/code/VoteErrorCode.java new file mode 100644 index 00000000..be4aa2d4 --- /dev/null +++ b/src/main/java/com/umc/naoman/global/error/code/VoteErrorCode.java @@ -0,0 +1,18 @@ +package com.umc.naoman.global.error.code; + +import com.umc.naoman.global.error.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum VoteErrorCode implements ErrorCode { + VOTE_NOT_FOUND(404, "EV001", "해당 voteId를 가진 투표가 존재하지 않습니다."), + DUPLICATE_VOTE(400, "EV002", "이미 해당 사진에 투표하였습니다."), + + ; + + private final int status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/umc/naoman/global/result/code/AgendaResultCode.java b/src/main/java/com/umc/naoman/global/result/code/AgendaResultCode.java index b19acd24..fd9c09b1 100644 --- a/src/main/java/com/umc/naoman/global/result/code/AgendaResultCode.java +++ b/src/main/java/com/umc/naoman/global/result/code/AgendaResultCode.java @@ -8,6 +8,9 @@ @RequiredArgsConstructor public enum AgendaResultCode implements ResultCode { CREATE_AGENDA(200, "SA001", "새로운 안건을 성공적으로 생성하였습니다."), + AGENDA_DETAIL(200, "SA002", "상세 안건을 성공적으로 조회했습니다."), + GET_AGENDA_LIST(200, "SA000", "해당 공유 그룹의 안건 목록을 성공적으로 조회했습니다."), + ; private final int status; private final String code; diff --git a/src/main/java/com/umc/naoman/global/result/code/MemberResultCode.java b/src/main/java/com/umc/naoman/global/result/code/MemberResultCode.java index 430d6153..4d31ec57 100644 --- a/src/main/java/com/umc/naoman/global/result/code/MemberResultCode.java +++ b/src/main/java/com/umc/naoman/global/result/code/MemberResultCode.java @@ -11,7 +11,7 @@ public enum MemberResultCode implements ResultCode { LOGIN(200, "SM000", "성공적으로 로그인하였습니다."), MYPAGE_INFO(200, "SM001", "내 정보를 성공적으로 조회하였습니다."), EDIT_MYPAGE_INFO(200, "SM002", "내 정보를 성공적으로 수정하였습니다."), - CHECK_MEMBER_REGISTRATION(200, "SM000", "해당 이메일을 가진 회원의 가입 여부를 성공적으로 조회하였습니다."), + CHECK_MEMBER_REGISTRATION(200, "SM000", "해당 정보에 대응하는 회원의 가입 여부를 성공적으로 조회하였습니다."), MEMBER_INFO (200,"SM005","회원 정보를 성공적으로 조회하였습니다."), CHECK_MARKETING_AGREED(200,"SM006","마케팅동의여부를 성공적으로 조회하였습니다."), ; diff --git a/src/main/java/com/umc/naoman/global/result/code/VoteResultCode.java b/src/main/java/com/umc/naoman/global/result/code/VoteResultCode.java new file mode 100644 index 00000000..4d3776e0 --- /dev/null +++ b/src/main/java/com/umc/naoman/global/result/code/VoteResultCode.java @@ -0,0 +1,19 @@ +package com.umc.naoman.global.result.code; + +import com.umc.naoman.global.result.ResultCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum VoteResultCode implements ResultCode { + GENERATE_VOTE(200, "SV001", "특정 안건에 대하여 성공적으로 투표하였습니다."), + GET_VOTE_LIST(200, "SV002", "특정 안건에 대한 투표 목록을 성공적으로 조회하였습니다."), + DELETE_VOTE(200, "SV003", "특정 안건에 대한 투표를 성공적으로 삭제하였습니다."), + + ; + + private final int status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/umc/naoman/global/security/util/CookieUtils.java b/src/main/java/com/umc/naoman/global/security/util/CookieUtils.java index eba6a9ae..2d8e3f06 100644 --- a/src/main/java/com/umc/naoman/global/security/util/CookieUtils.java +++ b/src/main/java/com/umc/naoman/global/security/util/CookieUtils.java @@ -16,9 +16,9 @@ public static void addCookie(HttpServletResponse response, String name, String v ResponseCookie cookie = ResponseCookie.from(name, value) .path("/") .maxAge(maxAge) - .httpOnly(false) - .secure(true) - .sameSite("None") + //.httpOnly(false) + //.secure(true) + //.sameSite("None") .build(); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());