diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java new file mode 100644 index 00000000..6b1eeebf --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertAdminController.java @@ -0,0 +1,52 @@ +package com.back.web7_9_codecrete_be.domain.concerts.controller; + +import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concert.ConcertListResponse; +import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concertPlace.ConcertPlaceListResponse; +import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertItem; +import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertUpdateRequest; +import com.back.web7_9_codecrete_be.domain.concerts.service.ConcertService; +import com.back.web7_9_codecrete_be.domain.concerts.service.KopisApiService; +import com.back.web7_9_codecrete_be.global.rsData.RsData; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/admin/concerts/") +@Tag(name = "Concerts Admin", description = "공연에 대해서 관리하는 API입니다. ") +public class ConcertAdminController { // todo : 인증 권한 추가하기 + private final ConcertService concertService; + private final KopisApiService kopisApiService; + + @GetMapping("tests") + public ConcertListResponse tests() { + return kopisApiService.getConcertsList(); + } + + @GetMapping("totalGetTest") + public ConcertListResponse totalGetTest() throws InterruptedException { + return kopisApiService.setConcertsList(); + } + + @GetMapping("setConcertPlace") + public ConcertPlaceListResponse setConcertPlace() throws InterruptedException { + return kopisApiService.setConcertPlace(); + } + + @PatchMapping("updateConcert/{concertId}") + public RsData updateConcert( + @PathVariable Long concertId, + @RequestBody ConcertUpdateRequest concertUpdateRequest + ){ + ConcertItem concertItem = concertService.updateConcert(concertId, concertUpdateRequest); + return RsData.success("공연 정보 수정이 완료되었습니다.",concertItem); + } + + @DeleteMapping("deleteConcert/{concertId}") + public RsData deleteConcert(@PathVariable Long concertId){ + concertService.deleteConcert(concertId); + return RsData.success("공연 정보 삭제에 성공하였습니다.",null); + } +} diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java index 5459d2f6..b28084d8 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/controller/ConcertController.java @@ -4,10 +4,13 @@ import com.back.web7_9_codecrete_be.domain.concerts.dto.KopisApiDto.concertPlace.ConcertPlaceListResponse; import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertDetailResponse; import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertItem; +import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertLikeResponse; import com.back.web7_9_codecrete_be.domain.concerts.dto.ticketOffice.TicketOfficeElement; import com.back.web7_9_codecrete_be.domain.concerts.entity.TicketOffice; import com.back.web7_9_codecrete_be.domain.concerts.service.ConcertService; import com.back.web7_9_codecrete_be.domain.concerts.service.KopisApiService; +import com.back.web7_9_codecrete_be.domain.users.entity.User; +import com.back.web7_9_codecrete_be.global.rq.Rq; import com.back.web7_9_codecrete_be.global.rsData.RsData; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Schema; @@ -19,10 +22,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; @@ -33,45 +33,36 @@ @Tag(name = "Concerts", description = "공연에 대한 정보를 제공하는 API 입니다.") public class ConcertController { private final ConcertService concertService; - private final KopisApiService kopisApiService; - - @GetMapping("tests") - public ConcertListResponse tests() { - return kopisApiService.getConcertsList(); - } - - @GetMapping("totalGetTest") - public ConcertListResponse totalGetTest() throws InterruptedException { - return kopisApiService.setConcertsList(); - } - - @GetMapping("setConcertPlace") - public ConcertPlaceListResponse setConcertPlace() throws InterruptedException { - return kopisApiService.setConcertPlace(); - } + private final Rq rq; @Operation(summary = "공연목록", description = "공연 전체 목록을 조회합니다. 시작일자를 기준으로 오름차순 조회합니다.") @GetMapping("list") public RsData> getList ( - @RequestParam - @Schema(description = "page입니다. 일단은 ?page={page} 로 넘기시면 됩니다.", example = "1") - int page + @Schema(description = "페이징 처리 또는 무한 스크롤 구현에 쓸 Pageable 객체입니다.") + Pageable pageable ) { - Pageable pageable = PageRequest.of(page, 10, Sort.by("startDate").ascending()); return RsData.success(concertService.getConcertsList(pageable)); } @Operation(summary = "다가오는 공연 목록", description = "오늘을 기준으로 다가오는 공연 목록을 조회합니다.") @GetMapping("upComingList") public RsData> getUpComingList ( - @RequestParam - @Schema(description = "page입니다. 일단은 ?page={page} 로 넘기시면 됩니다.", example = "1") - int page + @Schema(description = "페이징 처리 또는 무한 스크롤 구현에 쓸 Pageable 객체입니다.") + Pageable pageable ) { - Pageable pageable = PageRequest.of(page, 10); return RsData.success(concertService.getUpcomingConcertsList(pageable)); } + @Operation(summary = "좋아요 한 공연 조회", description = "좋아요를 누른 공연에 대한 목록을 조회합니다. 저장 날짜를 기준으로 내림차순 정렬로 표시합니다.(최신으로 추가된 목록순입니다.)") + @GetMapping("likedConcertList") + public RsData> getLikedConcertList ( + @Schema(description = "페이징 처리 또는 무한 스크롤 구현에 쓸 Pageable 객체입니다.") + Pageable pageable + ){ + User user = rq.getUser(); + return RsData.success(concertService.getLikedConcertsList(pageable,user)); + } + @Operation(summary = "공연 상세 조회", description = "공연에 대한 상세 목록을 조회합니다.") @GetMapping("concertDetail") public ConcertDetailResponse getConcertDetail( @@ -82,6 +73,8 @@ public ConcertDetailResponse getConcertDetail( return concertService.getConcertDetail(concertId); } + + @Operation(summary = "공연 예매처 조회", description = "공연에 대한 예매처들을 조회합니다.") @GetMapping("ticketOffices") public RsData> getTicketOffices ( @@ -92,4 +85,34 @@ public RsData> getTicketOffices ( return RsData.success(concertService.getTicketOfficesList(concertId)); } + @Operation(summary = "공연 좋아요 기능", description = "사용자가 마음에 드는 공연에 대해 좋아요를 통해 저장할 수 있습니다.") + @PostMapping("like/{concertId}") + public RsData likeConcert( + @PathVariable long concertId + ) { + User user = rq.getUser(); + concertService.likeConcert(concertId, user); + return RsData.success(null); + } + + @Operation(summary = "공연 좋아요 해제 기능", description = "좋아요를 해제할 수 있습니다.") + @DeleteMapping("dislike/{concertId}") + public RsData dislikeConcert( + @PathVariable long concertId + ) { + User user = rq.getUser(); + concertService.dislikeConcert(concertId, user); + return RsData.success(null); + } + + @Operation(summary = "공연 좋아요 여부 확인", description = "좋아요 여부를 확인합니다.") + @GetMapping("isLike/{concertId}") + public RsData isLikeConcert( + @PathVariable long concertId + ){ + User user = rq.getUser(); + return RsData.success(concertService.isLikeConcert(concertId, user)); + } + + } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertLikeResponse.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertLikeResponse.java new file mode 100644 index 00000000..919e6f5d --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertLikeResponse.java @@ -0,0 +1,16 @@ +package com.back.web7_9_codecrete_be.domain.concerts.dto.concert; + +import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert; +import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertLike; +import lombok.Getter; + +@Getter +public class ConcertLikeResponse { + private Long concertId; + private Boolean isLike; + + public ConcertLikeResponse(Concert concert, Boolean isLike) { + this.concertId = concert.getConcertId(); + this.isLike = isLike; + } +} diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertUpdateRequest.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertUpdateRequest.java new file mode 100644 index 00000000..69d61b9b --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/dto/concert/ConcertUpdateRequest.java @@ -0,0 +1,18 @@ +package com.back.web7_9_codecrete_be.domain.concerts.dto.concert; + +import lombok.Getter; + +import java.time.LocalDate; + +@Getter +public class ConcertUpdateRequest { + private Long concertId; + private String name; + private String description; + private Long placeId; + private LocalDate StartDate; + private LocalDate EndDate; + private String posterUrl; + private int maxPrice; + private int minPrice; +} diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/Concert.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/Concert.java index b3e6ecde..36733a36 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/Concert.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/Concert.java @@ -1,5 +1,6 @@ package com.back.web7_9_codecrete_be.domain.concerts.entity; +import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertUpdateRequest; import jakarta.persistence.*; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -90,4 +91,18 @@ public Concert update(ConcertPlace concertPlace, String content, String ticketTi return this; } + + public Concert update(ConcertUpdateRequest concertUpdateRequest,ConcertPlace concertPlace) { + this.name = concertUpdateRequest.getName(); + this.concertPlace = concertPlace; + this.content = concertUpdateRequest.getDescription(); + this.maxPrice = concertUpdateRequest.getMaxPrice(); + this.minPrice = concertUpdateRequest.getMinPrice(); + this.posterUrl = concertUpdateRequest.getPosterUrl(); + this.startDate = concertUpdateRequest.getStartDate(); + this.endDate = concertUpdateRequest.getEndDate(); + this.modifiedDate = LocalDateTime.now(); + return this; + } + } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/ConcertLike.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/ConcertLike.java index fbdb9267..0d37d309 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/ConcertLike.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/entity/ConcertLike.java @@ -4,6 +4,9 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; @Entity @Getter @@ -15,9 +18,17 @@ public class ConcertLike { @Column(name = "concert_like_id") private Long concertLikeId; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) private Concert concert; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) private User user; + + @CreationTimestamp + private LocalDateTime createdAt; + + public ConcertLike(Concert concert, User user) { + this.concert = concert; + this.user = user; + } } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertLikeRepository.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertLikeRepository.java index 928be3c3..0f616737 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertLikeRepository.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertLikeRepository.java @@ -1,10 +1,15 @@ package com.back.web7_9_codecrete_be.domain.concerts.repository; +import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert; import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertLike; +import com.back.web7_9_codecrete_be.domain.users.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface ConcertLikeRepository extends JpaRepository { + ConcertLike findConcertLikeByConcertAndUser(Concert concert, User user); + + boolean existsConcertLikeByConcertAndUser(Concert concert, User user); } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertRepository.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertRepository.java index 74454d26..66c48830 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertRepository.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/repository/ConcertRepository.java @@ -5,6 +5,7 @@ import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert; import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertPlace; import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertTime; +import com.back.web7_9_codecrete_be.domain.users.entity.User; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -65,6 +66,35 @@ List getUpComingConcertItems( @Param("fromDate") LocalDate fromDate ); + @Query(""" + SELECT + new com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertItem( + c.concertId as id, + c.name as name, + c.concertPlace.placeName as placeName, + c.startDate as startDate, + c.endDate as endDate, + c.posterUrl as posterUrl, + c.maxPrice as maxPrice, + c.minPrice as minPrice, + c.viewCount as viewCount, + c.likeCount as likeCount + ) + FROM + Concert c, + ConcertLike cl + WHERE + c.concertId = cl.concert.concertId + AND + cl.user.id = :userId + ORDER BY + cl.createdAt + DESC +""" + ) + List getLikedConcertsList(Pageable pageable, + @Param("userId") Long userId); + @Query(""" SELECT new com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertDetailResponse( @@ -86,4 +116,8 @@ List getUpComingConcertItems( c.concertId = :concertId """) ConcertDetailResponse getConcertDetailById(@Param("concertId")long concertId); + + + + Concert getConcertByConcertId(Long concertId); } diff --git a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/ConcertService.java b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/ConcertService.java index 80b0c172..7ac6f001 100644 --- a/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/ConcertService.java +++ b/src/main/java/com/back/web7_9_codecrete_be/domain/concerts/service/ConcertService.java @@ -2,14 +2,26 @@ import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertDetailResponse; import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertItem; +import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertLikeResponse; +import com.back.web7_9_codecrete_be.domain.concerts.dto.concert.ConcertUpdateRequest; import com.back.web7_9_codecrete_be.domain.concerts.dto.ticketOffice.TicketOfficeElement; import com.back.web7_9_codecrete_be.domain.concerts.entity.Concert; +import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertLike; +import com.back.web7_9_codecrete_be.domain.concerts.entity.ConcertPlace; import com.back.web7_9_codecrete_be.domain.concerts.entity.TicketOffice; +import com.back.web7_9_codecrete_be.domain.concerts.repository.ConcertLikeRepository; import com.back.web7_9_codecrete_be.domain.concerts.repository.ConcertPlaceRepository; import com.back.web7_9_codecrete_be.domain.concerts.repository.ConcertRepository; import com.back.web7_9_codecrete_be.domain.concerts.repository.TicketOfficeRepository; +import com.back.web7_9_codecrete_be.domain.users.entity.User; +import com.back.web7_9_codecrete_be.domain.users.repository.UserRepository; +import com.back.web7_9_codecrete_be.global.error.code.AuthErrorCode; +import com.back.web7_9_codecrete_be.global.error.code.ConcertErrorCode; +import com.back.web7_9_codecrete_be.global.error.code.ErrorCode; +import com.back.web7_9_codecrete_be.global.error.exception.BusinessException; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; +import org.springframework.security.core.parameters.P; import org.springframework.stereotype.Service; import java.time.LocalDate; @@ -21,17 +33,24 @@ public class ConcertService { private final ConcertRepository concertRepository; + private final ConcertLikeRepository concertLikeRepository; + private final ConcertPlaceRepository concertPlaceRepository; private final TicketOfficeRepository ticketOfficeRepository; + private final UserRepository userRepository; + public List getConcertsList(Pageable pageable) { return concertRepository.getConcertItems(pageable); } public List getUpcomingConcertsList(Pageable pageable) { - LocalDate today = LocalDate.now(); - return concertRepository.getUpComingConcertItems(pageable, today); + return concertRepository.getUpComingConcertItems(pageable, LocalDate.now()); + } + + public List getLikedConcertsList(Pageable pageable,User user) { + return concertRepository.getLikedConcertsList(pageable, user.getId()); } public ConcertDetailResponse getConcertDetail(long concertId) { @@ -60,5 +79,48 @@ public List getTicketOfficesList(long concertId) { return ticketOfficeList; } + public ConcertLikeResponse isLikeConcert(Long concertId, User user) { + Concert concert = concertRepository.getConcertByConcertId(concertId); + ConcertLikeResponse concertLikeResponse; + if(concertLikeRepository.existsConcertLikeByConcertAndUser(concert,user)){ + concertLikeResponse = new ConcertLikeResponse(concert,true); + } else { + concertLikeResponse = new ConcertLikeResponse(concert,false); + } + + return concertLikeResponse; + } + + public void likeConcert(long concertId, User user) { + Concert concert = concertRepository.findById(concertId).orElseThrow(); + if(concertLikeRepository.existsConcertLikeByConcertAndUser(concert,user)){ + throw new BusinessException(ConcertErrorCode.LIKE_CONFLICT); + } + ConcertLike concertLike = new ConcertLike(concert, user); + concertLikeRepository.save(concertLike); + } + + public void dislikeConcert(long concertId, User user) { + Concert concert = concertRepository.findById(concertId).orElseThrow(); + ConcertLike concertLike = concertLikeRepository.findConcertLikeByConcertAndUser(concert, user); + if(concertLike == null){ + throw new BusinessException(ConcertErrorCode.NOT_FOUND_CONCERTLIKE); + } + concertLikeRepository.delete(concertLike); + } + + public ConcertItem updateConcert(long concertId, ConcertUpdateRequest concertUpdateRequest) { + Concert concert = concertRepository.findById(concertId).orElseThrow(); + ConcertPlace concertPlace = concertPlaceRepository.findById(concertUpdateRequest.getPlaceId()).orElseThrow(); + concert.update(concertUpdateRequest, concertPlace); + Concert updatedConcert = concertRepository.save(concert); + return new ConcertItem(updatedConcert); + } + + public void deleteConcert(long concertId) { + Concert concert = concertRepository.findById(concertId).orElseThrow(); + concertRepository.deleteById(concertId); + } + } diff --git a/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ConcertErrorCode.java b/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ConcertErrorCode.java new file mode 100644 index 00000000..01b7bd8c --- /dev/null +++ b/src/main/java/com/back/web7_9_codecrete_be/global/error/code/ConcertErrorCode.java @@ -0,0 +1,18 @@ +package com.back.web7_9_codecrete_be.global.error.code; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ConcertErrorCode implements ErrorCode { + + LIKE_CONFLICT(HttpStatus.CONFLICT,"C131","이미 좋아요를 누른 공연입니다."), + NOT_FOUND_CONCERTLIKE(HttpStatus.NOT_FOUND,"C130","좋아요를 누르지 않은 공연입니다.") + ; + + private final HttpStatus status; + private final String code; + private final String message; +}